edbe8380d6d86d9166c0f2a301525d8f7b29c26b
[platform/upstream/cmake.git] / Source / CPack / cmCPackPackageMakerGenerator.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
4
5   Distributed under the OSI-approved BSD License (the "License");
6   see accompanying file Copyright.txt for details.
7
8   This software is distributed WITHOUT ANY WARRANTY; without even the
9   implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10   See the License for more information.
11 ============================================================================*/
12 #include "cmCPackPackageMakerGenerator.h"
13
14 #include "cmake.h"
15 #include "cmGlobalGenerator.h"
16 #include "cmLocalGenerator.h"
17 #include "cmSystemTools.h"
18 #include "cmMakefile.h"
19 #include "cmGeneratedFileStream.h"
20 #include "cmCPackComponentGroup.h"
21 #include "cmCPackLog.h"
22
23 #include <cmsys/SystemTools.hxx>
24 #include <cmsys/Glob.hxx>
25
26 //----------------------------------------------------------------------
27 cmCPackPackageMakerGenerator::cmCPackPackageMakerGenerator()
28 {
29   this->PackageMakerVersion = 0.0;
30   this->PackageCompatibilityVersion = 10.4;
31 }
32
33 //----------------------------------------------------------------------
34 cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator()
35 {
36 }
37
38 //----------------------------------------------------------------------
39 bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const
40 {
41   return this->PackageCompatibilityVersion >= 10.4;
42 }
43
44 //----------------------------------------------------------------------
45 int cmCPackPackageMakerGenerator::CopyInstallScript(const char* resdir,
46                                                     const char* script,
47                                                     const char* name)
48 {
49   std::string dst = resdir;
50   dst += "/";
51   dst += name;
52   cmSystemTools::CopyFileAlways(script, dst.c_str());
53   cmSystemTools::SetPermissions(dst.c_str(),0777);
54   cmCPackLogger(cmCPackLog::LOG_VERBOSE,
55                 "copy script : " << script << "\ninto " << dst.c_str() <<
56                 std::endl);
57
58   return 1;
59 }
60
61 //----------------------------------------------------------------------
62 int cmCPackPackageMakerGenerator::PackageFiles()
63 {
64   // TODO: Use toplevel
65   //       It is used! Is this an obsolete comment?
66
67   std::string resDir; // Where this package's resources will go.
68   std::string packageDirFileName
69     = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
70   if (this->Components.empty())
71     {
72     packageDirFileName += ".pkg";
73     resDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
74     resDir += "/Resources";
75     }
76   else
77     {
78     packageDirFileName += ".mpkg";
79     if ( !cmsys::SystemTools::MakeDirectory(packageDirFileName.c_str()))
80       {
81       cmCPackLogger(cmCPackLog::LOG_ERROR,
82                     "unable to create package directory "
83                     << packageDirFileName << std::endl);
84         return 0;
85       }
86
87     resDir = packageDirFileName;
88     resDir += "/Contents";
89     if ( !cmsys::SystemTools::MakeDirectory(resDir.c_str()))
90       {
91       cmCPackLogger(cmCPackLog::LOG_ERROR,
92                     "unable to create package subdirectory " << resDir
93                     << std::endl);
94         return 0;
95       }
96
97     resDir += "/Resources";
98     if ( !cmsys::SystemTools::MakeDirectory(resDir.c_str()))
99       {
100       cmCPackLogger(cmCPackLog::LOG_ERROR,
101                     "unable to create package subdirectory " << resDir
102                     << std::endl);
103         return 0;
104       }
105
106     resDir += "/en.lproj";
107     }
108
109
110   // Create directory structure
111   std::string preflightDirName = resDir + "/PreFlight";
112   std::string postflightDirName = resDir + "/PostFlight";
113   const char* preflight = this->GetOption("CPACK_PREFLIGHT_SCRIPT");
114   const char* postflight = this->GetOption("CPACK_POSTFLIGHT_SCRIPT");
115   const char* postupgrade = this->GetOption("CPACK_POSTUPGRADE_SCRIPT");
116   // if preflight or postflight scripts not there create directories
117   // of the same name, I think this makes it work
118   if(!preflight)
119     {
120     if ( !cmsys::SystemTools::MakeDirectory(preflightDirName.c_str()))
121       {
122       cmCPackLogger(cmCPackLog::LOG_ERROR,
123                     "Problem creating installer directory: "
124                     << preflightDirName.c_str() << std::endl);
125       return 0;
126       }
127     }
128   if(!postflight)
129     {
130     if ( !cmsys::SystemTools::MakeDirectory(postflightDirName.c_str()))
131       {
132       cmCPackLogger(cmCPackLog::LOG_ERROR,
133                     "Problem creating installer directory: "
134                     << postflightDirName.c_str() << std::endl);
135       return 0;
136       }
137     }
138   // if preflight, postflight, or postupgrade are set
139   // then copy them into the resource directory and make
140   // them executable
141   if(preflight)
142     {
143     this->CopyInstallScript(resDir.c_str(),
144                             preflight,
145                             "preflight");
146     }
147   if(postflight)
148     {
149     this->CopyInstallScript(resDir.c_str(),
150                             postflight,
151                             "postflight");
152     }
153   if(postupgrade)
154     {
155     this->CopyInstallScript(resDir.c_str(),
156                             postupgrade,
157                             "postupgrade");
158     }
159
160   if (!this->Components.empty())
161     {
162     // Create the directory where component packages will be built.
163     std::string basePackageDir = packageDirFileName;
164     basePackageDir += "/Contents/Packages";
165     if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str()))
166       {
167       cmCPackLogger(cmCPackLog::LOG_ERROR,
168                     "Problem creating component packages directory: "
169                     << basePackageDir.c_str() << std::endl);
170       return 0;
171       }
172
173     // Create the directory where downloaded component packages will
174     // be placed.
175     const char* userUploadDirectory =
176       this->GetOption("CPACK_UPLOAD_DIRECTORY");
177     std::string uploadDirectory;
178     if (userUploadDirectory && *userUploadDirectory)
179       {
180       uploadDirectory = userUploadDirectory;
181       }
182     else
183       {
184       uploadDirectory= this->GetOption("CPACK_PACKAGE_DIRECTORY");
185       uploadDirectory += "/CPackUploads";
186       }
187
188     // Create packages for each component
189     bool warnedAboutDownloadCompatibility = false;
190
191     std::map<std::string, cmCPackComponent>::iterator compIt;
192     for (compIt = this->Components.begin(); compIt != this->Components.end();
193          ++compIt)
194       {
195       std::string packageFile;
196       if (compIt->second.IsDownloaded)
197         {
198         if (this->PackageCompatibilityVersion >= 10.5 &&
199             this->PackageMakerVersion >= 3.0)
200           {
201           // Build this package within the upload directory.
202           packageFile = uploadDirectory;
203
204           if(!cmSystemTools::FileExists(uploadDirectory.c_str()))
205             {
206             if (!cmSystemTools::MakeDirectory(uploadDirectory.c_str()))
207               {
208               cmCPackLogger(cmCPackLog::LOG_ERROR,
209                             "Unable to create package upload directory "
210                             << uploadDirectory << std::endl);
211               return 0;
212               }
213             }
214           }
215         else if (!warnedAboutDownloadCompatibility)
216           {
217           if (this->PackageCompatibilityVersion < 10.5)
218             {
219             cmCPackLogger(
220               cmCPackLog::LOG_WARNING,
221               "CPack warning: please set CPACK_OSX_PACKAGE_VERSION to 10.5 "
222               "or greater enable downloaded packages. CPack will build a "
223               "non-downloaded package."
224               << std::endl);
225             }
226
227           if (this->PackageMakerVersion < 3)
228             {
229             cmCPackLogger(cmCPackLog::LOG_WARNING,
230                         "CPack warning: unable to build downloaded "
231                           "packages with PackageMaker versions prior "
232                           "to 3.0. CPack will build a non-downloaded package."
233                           << std::endl);
234             }
235
236           warnedAboutDownloadCompatibility = true;
237           }
238         }
239
240       if (packageFile.empty())
241         {
242         // Build this package within the overall distribution
243         // metapackage.
244         packageFile = basePackageDir;
245
246         // We're not downloading this component, even if the user
247         // requested it.
248         compIt->second.IsDownloaded = false;
249         }
250
251       packageFile += '/';
252       packageFile += GetPackageName(compIt->second);
253
254       std::string packageDir = toplevel;
255       packageDir += '/';
256       packageDir += compIt->first;
257       if (!this->GenerateComponentPackage(packageFile.c_str(),
258                                           packageDir.c_str(),
259                                           compIt->second))
260         {
261         return 0;
262         }
263       }
264     }
265   this->SetOption("CPACK_MODULE_VERSION_SUFFIX", "");
266
267   // Copy or create all of the resource files we need.
268   if ( !this->CopyCreateResourceFile("License", resDir.c_str())
269        || !this->CopyCreateResourceFile("ReadMe", resDir.c_str())
270        || !this->CopyCreateResourceFile("Welcome", resDir.c_str())
271        || !this->CopyResourcePlistFile("Info.plist")
272        || !this->CopyResourcePlistFile("Description.plist") )
273     {
274     cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem copying the resource files"
275       << std::endl);
276     return 0;
277     }
278
279   if (this->Components.empty())
280     {
281     // Use PackageMaker to build the package.
282     cmOStringStream pkgCmd;
283     pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
284            << "\" -build -p \"" << packageDirFileName << "\"";
285     if (this->Components.empty())
286       {
287       pkgCmd << " -f \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY");
288       }
289     else
290       {
291       pkgCmd << " -mi \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY")
292              << "/packages/";
293       }
294     pkgCmd << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
295            << "/Resources\" -i \""
296            << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
297            << "/Info.plist\" -d \""
298            << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
299            << "/Description.plist\"";
300     if ( this->PackageMakerVersion > 2.0 )
301       {
302       pkgCmd << " -v";
303       }
304     if (!RunPackageMaker(pkgCmd.str().c_str(), packageDirFileName.c_str()))
305       return 0;
306     }
307   else
308     {
309     // We have built the package in place. Generate the
310     // distribution.dist file to describe it for the installer.
311     WriteDistributionFile(packageDirFileName.c_str());
312     }
313
314   std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
315   tmpFile += "/hdiutilOutput.log";
316   cmOStringStream dmgCmd;
317   dmgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM_DISK_IMAGE")
318     << "\" create -ov -format UDZO -srcfolder \"" << packageDirFileName
319     << "\" \"" << packageFileNames[0] << "\"";
320   std::string output;
321   int retVal = 1;
322   int numTries = 10;
323   bool res = false;
324   while(numTries > 0)
325     {
326     res = cmSystemTools::RunSingleCommand(dmgCmd.str().c_str(), &output,
327                                           &retVal, 0, this->GeneratorVerbose,
328                                           0);
329     if ( res && !retVal )
330       {
331       numTries = -1;
332       break;
333       }
334     cmSystemTools::Delay(500);
335     numTries--;
336     }
337   if ( !res || retVal )
338     {
339     cmGeneratedFileStream ofs(tmpFile.c_str());
340     ofs << "# Run command: " << dmgCmd.str().c_str() << std::endl
341       << "# Output:" << std::endl
342       << output.c_str() << std::endl;
343     cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running hdiutil command: "
344       << dmgCmd.str().c_str() << std::endl
345       << "Please check " << tmpFile.c_str() << " for errors" << std::endl);
346     return 0;
347     }
348
349   return 1;
350 }
351
352 //----------------------------------------------------------------------
353 int cmCPackPackageMakerGenerator::InitializeInternal()
354 {
355   cmCPackLogger(cmCPackLog::LOG_DEBUG,
356     "cmCPackPackageMakerGenerator::Initialize()" << std::endl);
357   this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr");
358
359   // Starting with Xcode 4.3, PackageMaker is a separate app, and you
360   // can put it anywhere you want. So... use a variable for its location.
361   // People who put it in unexpected places can use the variable to tell
362   // us where it is.
363   //
364   // Use the following locations, in "most recent installation" order,
365   // to search for the PackageMaker app. Assume people who copy it into
366   // the new Xcode 4.3 app in "/Applications" will copy it into the nested
367   // Applications folder inside the Xcode bundle itself. Or directly in
368   // the "/Applications" directory.
369   //
370   // If found, save result in the CPACK_INSTALLER_PROGRAM variable.
371
372   std::vector<std::string> paths;
373   paths.push_back(
374     "/Applications/Xcode.app/Contents/Applications"
375     "/PackageMaker.app/Contents/MacOS");
376   paths.push_back(
377     "/Applications/Utilities"
378     "/PackageMaker.app/Contents/MacOS");
379   paths.push_back(
380     "/Applications"
381     "/PackageMaker.app/Contents/MacOS");
382   paths.push_back(
383     "/Developer/Applications/Utilities"
384     "/PackageMaker.app/Contents/MacOS");
385   paths.push_back(
386     "/Developer/Applications"
387     "/PackageMaker.app/Contents/MacOS");
388
389   std::string pkgPath;
390   const char *inst_program = this->GetOption("CPACK_INSTALLER_PROGRAM");
391   if (inst_program && *inst_program)
392     {
393     pkgPath = inst_program;
394     }
395   else
396     {
397     pkgPath = cmSystemTools::FindProgram("PackageMaker", paths, false);
398     if ( pkgPath.empty() )
399       {
400       cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find PackageMaker compiler"
401         << std::endl);
402       return 0;
403       }
404     this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", pkgPath.c_str());
405     }
406
407   // Get path to the real PackageMaker, not a symlink:
408   pkgPath = cmSystemTools::GetRealPath(pkgPath.c_str());
409   // Up from there to find the version.plist file in the "Contents" dir:
410   std::string contents_dir;
411   contents_dir = cmSystemTools::GetFilenamePath(pkgPath);
412   contents_dir = cmSystemTools::GetFilenamePath(contents_dir);
413
414   std::string versionFile = contents_dir + "/version.plist";
415
416   if ( !cmSystemTools::FileExists(versionFile.c_str()) )
417     {
418     cmCPackLogger(cmCPackLog::LOG_ERROR,
419       "Cannot find PackageMaker compiler version file: "
420       << versionFile.c_str()
421       << std::endl);
422     return 0;
423     }
424
425   std::ifstream ifs(versionFile.c_str());
426   if ( !ifs )
427     {
428     cmCPackLogger(cmCPackLog::LOG_ERROR,
429       "Cannot open PackageMaker compiler version file" << std::endl);
430     return 0;
431     }
432
433   // Check the PackageMaker version
434   cmsys::RegularExpression rexKey("<key>CFBundleShortVersionString</key>");
435   cmsys::RegularExpression rexVersion("<string>([0-9]+.[0-9.]+)</string>");
436   std::string line;
437   bool foundKey = false;
438   while ( cmSystemTools::GetLineFromStream(ifs, line) )
439     {
440     if ( rexKey.find(line) )
441       {
442       foundKey = true;
443       break;
444       }
445     }
446   if ( !foundKey )
447     {
448     cmCPackLogger(cmCPackLog::LOG_ERROR,
449       "Cannot find CFBundleShortVersionString in the PackageMaker compiler "
450       "version file" << std::endl);
451     return 0;
452     }
453   if ( !cmSystemTools::GetLineFromStream(ifs, line) ||
454     !rexVersion.find(line) )
455     {
456     cmCPackLogger(cmCPackLog::LOG_ERROR,
457       "Problem reading the PackageMaker compiler version file: "
458       << versionFile.c_str() << std::endl);
459     return 0;
460     }
461   this->PackageMakerVersion = atof(rexVersion.match(1).c_str());
462   if ( this->PackageMakerVersion < 1.0 )
463     {
464     cmCPackLogger(cmCPackLog::LOG_ERROR, "Require PackageMaker 1.0 or higher"
465       << std::endl);
466     return 0;
467     }
468   cmCPackLogger(cmCPackLog::LOG_DEBUG, "PackageMaker version is: "
469     << this->PackageMakerVersion << std::endl);
470
471   // Determine the package compatibility version. If it wasn't
472   // specified by the user, we define it based on which features the
473   // user requested.
474   const char *packageCompat = this->GetOption("CPACK_OSX_PACKAGE_VERSION");
475   if (packageCompat && *packageCompat)
476     {
477     this->PackageCompatibilityVersion = atof(packageCompat);
478     }
479   else if (this->GetOption("CPACK_DOWNLOAD_SITE"))
480     {
481     this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.5");
482     this->PackageCompatibilityVersion = 10.5;
483     }
484   else if (this->GetOption("CPACK_COMPONENTS_ALL"))
485     {
486     this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.4");
487     this->PackageCompatibilityVersion = 10.4;
488     }
489   else
490     {
491     this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.3");
492     this->PackageCompatibilityVersion = 10.3;
493     }
494
495   std::vector<std::string> no_paths;
496   pkgPath = cmSystemTools::FindProgram("hdiutil", no_paths, false);
497   if ( pkgPath.empty() )
498     {
499     cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find hdiutil compiler"
500       << std::endl);
501     return 0;
502     }
503   this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM_DISK_IMAGE",
504                           pkgPath.c_str());
505
506   return this->Superclass::InitializeInternal();
507 }
508
509 //----------------------------------------------------------------------
510 bool cmCPackPackageMakerGenerator::CopyCreateResourceFile(const char* name,
511                                                           const char* dirName)
512 {
513   std::string uname = cmSystemTools::UpperCase(name);
514   std::string cpackVar = "CPACK_RESOURCE_FILE_" + uname;
515   const char* inFileName = this->GetOption(cpackVar.c_str());
516   if ( !inFileName )
517     {
518     cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack option: " << cpackVar.c_str()
519                   << " not specified. It should point to "
520                   << (name ? name : "(NULL)")
521                   << ".rtf, " << name
522                   << ".html, or " << name << ".txt file" << std::endl);
523     return false;
524     }
525   if ( !cmSystemTools::FileExists(inFileName) )
526     {
527     cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find "
528                   << (name ? name : "(NULL)")
529                   << " resource file: " << inFileName << std::endl);
530     return false;
531     }
532   std::string ext = cmSystemTools::GetFilenameLastExtension(inFileName);
533   if ( ext != ".rtfd" && ext != ".rtf" && ext != ".html" && ext != ".txt" )
534     {
535     cmCPackLogger(cmCPackLog::LOG_ERROR, "Bad file extension specified: "
536       << ext << ". Currently only .rtfd, .rtf, .html, and .txt files allowed."
537       << std::endl);
538     return false;
539     }
540
541   std::string destFileName = dirName;
542   destFileName += '/';
543   destFileName += name + ext;
544
545   // Set this so that distribution.dist gets the right name (without
546   // the path).
547   this->SetOption(("CPACK_RESOURCE_FILE_" + uname + "_NOPATH").c_str(),
548                   (name + ext).c_str());
549
550   cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: "
551                 << (inFileName ? inFileName : "(NULL)")
552                 << " to " << destFileName.c_str() << std::endl);
553   this->ConfigureFile(inFileName, destFileName.c_str());
554   return true;
555 }
556
557 bool cmCPackPackageMakerGenerator::CopyResourcePlistFile(const char* name,
558                                                          const char* outName)
559 {
560   if (!outName)
561     {
562     outName = name;
563     }
564
565   std::string inFName = "CPack.";
566   inFName += name;
567   inFName += ".in";
568   std::string inFileName = this->FindTemplate(inFName.c_str());
569   if ( inFileName.empty() )
570     {
571     cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find input file: "
572       << inFName << std::endl);
573     return false;
574     }
575
576   std::string destFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
577   destFileName += "/";
578   destFileName += outName;
579
580   cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Configure file: "
581     << inFileName.c_str() << " to " << destFileName.c_str() << std::endl);
582   this->ConfigureFile(inFileName.c_str(), destFileName.c_str());
583   return true;
584 }
585
586 //----------------------------------------------------------------------
587 bool cmCPackPackageMakerGenerator::RunPackageMaker(const char *command,
588                                                    const char *packageFile)
589 {
590   std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
591   tmpFile += "/PackageMakerOutput.log";
592
593   cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl);
594   std::string output;
595   int retVal = 1;
596   bool res = cmSystemTools::RunSingleCommand(command, &output, &retVal, 0,
597                                              this->GeneratorVerbose, 0);
598   cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Done running package maker"
599     << std::endl);
600   if ( !res || retVal )
601     {
602     cmGeneratedFileStream ofs(tmpFile.c_str());
603     ofs << "# Run command: " << command << std::endl
604       << "# Output:" << std::endl
605       << output.c_str() << std::endl;
606     cmCPackLogger(cmCPackLog::LOG_ERROR,
607       "Problem running PackageMaker command: " << command
608       << std::endl << "Please check " << tmpFile.c_str() << " for errors"
609       << std::endl);
610     return false;
611     }
612   // sometimes the command finishes but the directory is not yet
613   // created, so try 10 times to see if it shows up
614   int tries = 10;
615   while(tries > 0 &&
616         !cmSystemTools::FileExists(packageFile))
617     {
618     cmSystemTools::Delay(500);
619     tries--;
620     }
621   if(!cmSystemTools::FileExists(packageFile))
622     {
623     cmCPackLogger(
624       cmCPackLog::LOG_ERROR,
625       "Problem running PackageMaker command: " << command
626       << std::endl << "Package not created: " << packageFile
627       << std::endl);
628     return false;
629     }
630
631   return true;
632 }
633
634 //----------------------------------------------------------------------
635 std::string
636 cmCPackPackageMakerGenerator::GetPackageName(const cmCPackComponent& component)
637 {
638   if (component.ArchiveFile.empty())
639     {
640     std::string packagesDir = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
641     packagesDir += ".dummy";
642     cmOStringStream out;
643     out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir)
644         << "-" << component.Name << ".pkg";
645     return out.str();
646     }
647   else
648     {
649     return component.ArchiveFile + ".pkg";
650     }
651 }
652
653 //----------------------------------------------------------------------
654 bool
655 cmCPackPackageMakerGenerator::
656 GenerateComponentPackage(const char *packageFile,
657                          const char *packageDir,
658                          const cmCPackComponent& component)
659 {
660   cmCPackLogger(cmCPackLog::LOG_OUTPUT,
661                 "-   Building component package: " <<
662                 packageFile << std::endl);
663
664   // The command that will be used to run PackageMaker
665   cmOStringStream pkgCmd;
666
667   if (this->PackageCompatibilityVersion < 10.5 ||
668       this->PackageMakerVersion < 3.0)
669     {
670     // Create Description.plist and Info.plist files for normal Mac OS
671     // X packages, which work on Mac OS X 10.3 and newer.
672     std::string descriptionFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
673     descriptionFile += '/' + component.Name + "-Description.plist";
674     std::ofstream out(descriptionFile.c_str());
675     out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl
676         << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\""
677         << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << std::endl
678         << "<plist version=\"1.4\">" << std::endl
679         << "<dict>" << std::endl
680         << "  <key>IFPkgDescriptionTitle</key>" << std::endl
681         << "  <string>" << component.DisplayName << "</string>" << std::endl
682         << "  <key>IFPkgDescriptionVersion</key>" << std::endl
683         << "  <string>" << this->GetOption("CPACK_PACKAGE_VERSION")
684         << "</string>" << std::endl
685         << "  <key>IFPkgDescriptionDescription</key>" << std::endl
686         << "  <string>" + this->EscapeForXML(component.Description)
687         << "</string>" << std::endl
688         << "</dict>" << std::endl
689         << "</plist>" << std::endl;
690     out.close();
691
692     // Create the Info.plist file for this component
693     std::string moduleVersionSuffix = ".";
694     moduleVersionSuffix += component.Name;
695     this->SetOption("CPACK_MODULE_VERSION_SUFFIX",
696                     moduleVersionSuffix.c_str());
697     std::string infoFileName = component.Name;
698     infoFileName += "-Info.plist";
699     if (!this->CopyResourcePlistFile("Info.plist", infoFileName.c_str()))
700       {
701       return false;
702       }
703
704     pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
705            << "\" -build -p \"" << packageFile << "\""
706            << " -f \"" << packageDir << "\""
707            << " -i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY")
708            << "/" << infoFileName << "\""
709            << " -d \"" << descriptionFile << "\"";
710     }
711   else
712     {
713     // Create a "flat" package on Mac OS X 10.5 and newer. Flat
714     // packages are stored in a single file, rather than a directory
715     // like normal packages, and can be downloaded by the installer
716     // on-the-fly in Mac OS X 10.5 or newer. Thus, we need to create
717     // flat packages when the packages will be downloaded on the fly.
718     std::string pkgId = "com.";
719     pkgId += this->GetOption("CPACK_PACKAGE_VENDOR");
720     pkgId += '.';
721     pkgId += this->GetOption("CPACK_PACKAGE_NAME");
722     pkgId += '.';
723     pkgId += component.Name;
724
725     pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM")
726            << "\" --root \"" << packageDir << "\""
727            << " --id " << pkgId
728            << " --target " << this->GetOption("CPACK_OSX_PACKAGE_VERSION")
729            << " --out \"" << packageFile << "\"";
730     }
731
732   // Run PackageMaker
733   return RunPackageMaker(pkgCmd.str().c_str(), packageFile);
734 }
735
736 //----------------------------------------------------------------------
737 void
738 cmCPackPackageMakerGenerator::
739 WriteDistributionFile(const char* metapackageFile)
740 {
741   std::string distributionTemplate
742     = this->FindTemplate("CPack.distribution.dist.in");
743   if ( distributionTemplate.empty() )
744     {
745     cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find input file: "
746       << distributionTemplate << std::endl);
747     return;
748     }
749
750   std::string distributionFile = metapackageFile;
751   distributionFile += "/Contents/distribution.dist";
752
753   // Create the choice outline, which provides a tree-based view of
754   // the components in their groups.
755   cmOStringStream choiceOut;
756   choiceOut << "<choices-outline>" << std::endl;
757
758   // Emit the outline for the groups
759   std::map<std::string, cmCPackComponentGroup>::iterator groupIt;
760   for (groupIt = this->ComponentGroups.begin();
761        groupIt != this->ComponentGroups.end();
762        ++groupIt)
763     {
764     if (groupIt->second.ParentGroup == 0)
765       {
766       CreateChoiceOutline(groupIt->second, choiceOut);
767       }
768     }
769
770   // Emit the outline for the non-grouped components
771   std::map<std::string, cmCPackComponent>::iterator compIt;
772   for (compIt = this->Components.begin(); compIt != this->Components.end();
773        ++compIt)
774     {
775     if (!compIt->second.Group)
776       {
777       choiceOut << "<line choice=\"" << compIt->first << "Choice\"></line>"
778                 << std::endl;
779       }
780     }
781   choiceOut << "</choices-outline>" << std::endl;
782
783   // Create the actual choices
784   for (groupIt = this->ComponentGroups.begin();
785        groupIt != this->ComponentGroups.end();
786        ++groupIt)
787     {
788     CreateChoice(groupIt->second, choiceOut);
789     }
790   for (compIt = this->Components.begin(); compIt != this->Components.end();
791        ++compIt)
792     {
793     CreateChoice(compIt->second, choiceOut);
794     }
795   this->SetOption("CPACK_PACKAGEMAKER_CHOICES", choiceOut.str().c_str());
796
797   // Create the distribution.dist file in the metapackage to turn it
798   // into a distribution package.
799   this->ConfigureFile(distributionTemplate.c_str(),
800                       distributionFile.c_str());
801 }
802
803 //----------------------------------------------------------------------
804 void
805 cmCPackPackageMakerGenerator::
806 CreateChoiceOutline(const cmCPackComponentGroup& group, cmOStringStream& out)
807 {
808   out << "<line choice=\"" << group.Name << "Choice\">" << std::endl;
809   std::vector<cmCPackComponentGroup*>::const_iterator groupIt;
810   for (groupIt = group.Subgroups.begin(); groupIt != group.Subgroups.end();
811        ++groupIt)
812     {
813     CreateChoiceOutline(**groupIt, out);
814     }
815
816   std::vector<cmCPackComponent*>::const_iterator compIt;
817   for (compIt = group.Components.begin(); compIt != group.Components.end();
818        ++compIt)
819     {
820     out << "  <line choice=\"" << (*compIt)->Name << "Choice\"></line>"
821         << std::endl;
822     }
823   out << "</line>" << std::endl;
824 }
825
826 //----------------------------------------------------------------------
827 void
828 cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponentGroup& group,
829                                            cmOStringStream& out)
830 {
831   out << "<choice id=\"" << group.Name << "Choice\" "
832       << "title=\"" << group.DisplayName << "\" "
833       << "start_selected=\"true\" "
834       << "start_enabled=\"true\" "
835       << "start_visible=\"true\" ";
836   if (!group.Description.empty())
837     {
838     out << "description=\"" << EscapeForXML(group.Description)
839         << "\"";
840     }
841   out << "></choice>" << std::endl;
842 }
843
844 //----------------------------------------------------------------------
845 void
846 cmCPackPackageMakerGenerator::CreateChoice(const cmCPackComponent& component,
847                                            cmOStringStream& out)
848 {
849   std::string packageId = "com.";
850   packageId += this->GetOption("CPACK_PACKAGE_VENDOR");
851   packageId += '.';
852   packageId += this->GetOption("CPACK_PACKAGE_NAME");
853   packageId += '.';
854   packageId += component.Name;
855
856   out << "<choice id=\"" << component.Name << "Choice\" "
857       << "title=\"" << component.DisplayName << "\" "
858       << "start_selected=\""
859       << (component.IsDisabledByDefault &&
860           !component.IsRequired? "false" : "true")
861       << "\" "
862       << "start_enabled=\""
863       << (component.IsRequired? "false" : "true")
864       << "\" "
865       << "start_visible=\"" << (component.IsHidden? "false" : "true") << "\" ";
866   if (!component.Description.empty())
867     {
868     out << "description=\"" << EscapeForXML(component.Description)
869         << "\" ";
870     }
871   if (!component.Dependencies.empty() ||
872       !component.ReverseDependencies.empty())
873     {
874     // The "selected" expression is evaluated each time any choice is
875     // selected, for all choices *except* the one that the user
876     // selected. A component is marked selected if it has been
877     // selected (my.choice.selected in Javascript) and all of the
878     // components it depends on have been selected (transitively) or
879     // if any of the components that depend on it have been selected
880     // (transitively). Assume that we have components A, B, C, D, and
881     // E, where each component depends on the previous component (B
882     // depends on A, C depends on B, D depends on C, and E depends on
883     // D). The expression we build for the component C will be
884     //   my.choice.selected && B && A || D || E
885     // This way, selecting C will automatically select everything it depends
886     // on (B and A), while selecting something that depends on C--either D
887     // or E--will automatically cause C to get selected.
888     out << "selected=\"my.choice.selected";
889     std::set<const cmCPackComponent *> visited;
890     AddDependencyAttributes(component, visited, out);
891     visited.clear();
892     AddReverseDependencyAttributes(component, visited, out);
893     out << "\"";
894     }
895   out << ">" << std::endl;
896   out << "  <pkg-ref id=\"" << packageId << "\"></pkg-ref>" << std::endl;
897   out << "</choice>" << std::endl;
898
899   // Create a description of the package associated with this
900   // component.
901   std::string relativePackageLocation = "Contents/Packages/";
902   relativePackageLocation += this->GetPackageName(component);
903
904   // Determine the installed size of the package.
905   std::string dirName = this->GetOption("CPACK_TEMPORARY_DIRECTORY");
906   dirName += '/';
907   dirName += component.Name;
908   unsigned long installedSize
909     = component.GetInstalledSizeInKbytes(dirName.c_str());
910
911   out << "<pkg-ref id=\"" << packageId << "\" "
912       << "version=\"" << this->GetOption("CPACK_PACKAGE_VERSION") << "\" "
913       << "installKBytes=\"" << installedSize << "\" "
914       << "auth=\"Admin\" onConclusion=\"None\">";
915   if (component.IsDownloaded)
916     {
917     out << this->GetOption("CPACK_DOWNLOAD_SITE")
918         << this->GetPackageName(component);
919     }
920   else
921     {
922     out << "file:./" << relativePackageLocation;
923     }
924   out << "</pkg-ref>" << std::endl;
925 }
926
927 //----------------------------------------------------------------------
928 void
929 cmCPackPackageMakerGenerator::
930 AddDependencyAttributes(const cmCPackComponent& component,
931                         std::set<const cmCPackComponent *>& visited,
932                         cmOStringStream& out)
933 {
934   if (visited.find(&component) != visited.end())
935     {
936     return;
937     }
938   visited.insert(&component);
939
940   std::vector<cmCPackComponent *>::const_iterator dependIt;
941   for (dependIt = component.Dependencies.begin();
942        dependIt != component.Dependencies.end();
943        ++dependIt)
944     {
945     out << " &amp;&amp; choices['" <<
946       (*dependIt)->Name << "Choice'].selected";
947     AddDependencyAttributes(**dependIt, visited, out);
948     }
949 }
950
951 //----------------------------------------------------------------------
952 void
953 cmCPackPackageMakerGenerator::
954 AddReverseDependencyAttributes(const cmCPackComponent& component,
955                                std::set<const cmCPackComponent *>& visited,
956                                cmOStringStream& out)
957 {
958   if (visited.find(&component) != visited.end())
959     {
960     return;
961     }
962   visited.insert(&component);
963
964   std::vector<cmCPackComponent *>::const_iterator dependIt;
965   for (dependIt = component.ReverseDependencies.begin();
966        dependIt != component.ReverseDependencies.end();
967        ++dependIt)
968     {
969     out << " || choices['" << (*dependIt)->Name << "Choice'].selected";
970     AddReverseDependencyAttributes(**dependIt, visited, out);
971     }
972 }
973
974 //----------------------------------------------------------------------
975 std::string cmCPackPackageMakerGenerator::EscapeForXML(std::string str)
976 {
977   cmSystemTools::ReplaceString(str, "&", "&amp;");
978   cmSystemTools::ReplaceString(str, "<", "&lt;");
979   cmSystemTools::ReplaceString(str, ">", "&gt;");
980   cmSystemTools::ReplaceString(str, "\"", "&quot;");
981   return str;
982 }