d973c01347ac5f2e0264aea1d9f94fd886973a09
[platform/upstream/cmake.git] / Source / CPack / cmCPackDragNDropGenerator.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
13 #include "cmCPackDragNDropGenerator.h"
14 #include "cmCPackLog.h"
15 #include "cmSystemTools.h"
16 #include "cmGeneratedFileStream.h"
17
18 #include <cmsys/RegularExpression.hxx>
19
20 static const char* SLAHeader =
21 "data 'LPic' (5000) {\n"
22 "    $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n"
23 "    $\"0008 0003 0000 0001 0004 0000 0004 0005\"\n"
24 "    $\"0000 000E 0006 0001 0005 0007 0000 0007\"\n"
25 "    $\"0008 0000 0047 0009 0000 0034 000A 0001\"\n"
26 "    $\"0035 000B 0001 0020 000C 0000 0011 000D\"\n"
27 "    $\"0000 005B 0004 0000 0033 000F 0001 000C\"\n"
28 "    $\"0010 0000 000B 000E 0000\"\n"
29 "};\n"
30 "\n";
31
32 static const char* SLASTREnglish =
33 "resource 'STR#' (5002, \"English\") {\n"
34 "    {\n"
35 "        \"English\",\n"
36 "        \"Agree\",\n"
37 "        \"Disagree\",\n"
38 "        \"Print\",\n"
39 "        \"Save...\",\n"
40 "        \"You agree to the License Agreement terms when you click \"\n"
41 "        \"the \\\"Agree\\\" button.\",\n"
42 "        \"Software License Agreement\",\n"
43 "        \"This text cannot be saved.  This disk may be full or locked, "
44 "or the \"\n"
45 "        \"file may be locked.\",\n"
46 "        \"Unable to print.  Make sure you have selected a printer.\"\n"
47 "    }\n"
48 "};\n"
49 "\n";
50
51 //----------------------------------------------------------------------
52 cmCPackDragNDropGenerator::cmCPackDragNDropGenerator()
53 {
54   // default to one package file for components
55   this->componentPackageMethod = ONE_PACKAGE;
56 }
57
58 //----------------------------------------------------------------------
59 cmCPackDragNDropGenerator::~cmCPackDragNDropGenerator()
60 {
61 }
62
63 //----------------------------------------------------------------------
64 int cmCPackDragNDropGenerator::InitializeInternal()
65 {
66   // Starting with Xcode 4.3, look in "/Applications/Xcode.app" first:
67   //
68   std::vector<std::string> paths;
69   paths.push_back("/Applications/Xcode.app/Contents/Developer/Tools");
70   paths.push_back("/Developer/Tools");
71
72   const std::string hdiutil_path = cmSystemTools::FindProgram("hdiutil",
73     std::vector<std::string>(), false);
74   if(hdiutil_path.empty())
75     {
76     cmCPackLogger(cmCPackLog::LOG_ERROR,
77       "Cannot locate hdiutil command"
78       << std::endl);
79     return 0;
80     }
81   this->SetOptionIfNotSet("CPACK_COMMAND_HDIUTIL", hdiutil_path.c_str());
82
83   const std::string setfile_path = cmSystemTools::FindProgram("SetFile",
84     paths, false);
85   if(setfile_path.empty())
86     {
87     cmCPackLogger(cmCPackLog::LOG_ERROR,
88       "Cannot locate SetFile command"
89       << std::endl);
90     return 0;
91     }
92   this->SetOptionIfNotSet("CPACK_COMMAND_SETFILE", setfile_path.c_str());
93
94   const std::string rez_path = cmSystemTools::FindProgram("Rez",
95     paths, false);
96   if(rez_path.empty())
97     {
98     cmCPackLogger(cmCPackLog::LOG_ERROR,
99       "Cannot locate Rez command"
100       << std::endl);
101     return 0;
102     }
103   this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path.c_str());
104
105   return this->Superclass::InitializeInternal();
106 }
107
108 //----------------------------------------------------------------------
109 const char* cmCPackDragNDropGenerator::GetOutputExtension()
110 {
111   return ".dmg";
112 }
113
114 //----------------------------------------------------------------------
115 int cmCPackDragNDropGenerator::PackageFiles()
116 {
117   // gather which directories to make dmg files for
118   // multiple directories occur if packaging components or groups separately
119
120   // monolith
121   if(this->Components.empty())
122     {
123     return this->CreateDMG(toplevel, packageFileNames[0]);
124     }
125
126   // component install
127   std::vector<std::string> package_files;
128
129   std::map<std::string, cmCPackComponent>::iterator compIt;
130   for (compIt=this->Components.begin();
131        compIt!=this->Components.end(); ++compIt )
132     {
133     std::string name = GetComponentInstallDirNameSuffix(compIt->first);
134     package_files.push_back(name);
135     }
136   std::sort(package_files.begin(), package_files.end());
137   package_files.erase(std::unique(package_files.begin(),
138                       package_files.end()),
139                       package_files.end());
140
141
142   // loop to create dmg files
143   packageFileNames.clear();
144   for(size_t i=0; i<package_files.size(); i++)
145     {
146     std::string full_package_name = std::string(toplevel) + std::string("/");
147     if(package_files[i] == "ALL_IN_ONE")
148       {
149       full_package_name += this->GetOption("CPACK_PACKAGE_FILE_NAME");
150       }
151     else
152       {
153       full_package_name += package_files[i];
154       }
155     full_package_name += std::string(GetOutputExtension());
156     packageFileNames.push_back(full_package_name);
157
158     std::string src_dir = toplevel;
159     src_dir += "/";
160     src_dir += package_files[i];
161
162     if(0 == this->CreateDMG(src_dir, full_package_name))
163       {
164       return 0;
165       }
166     }
167   return 1;
168 }
169
170 //----------------------------------------------------------------------
171 bool cmCPackDragNDropGenerator::CopyFile(cmOStringStream& source,
172   cmOStringStream& target)
173 {
174   if(!cmSystemTools::CopyFileIfDifferent(
175     source.str().c_str(),
176     target.str().c_str()))
177     {
178     cmCPackLogger(cmCPackLog::LOG_ERROR,
179       "Error copying "
180       << source.str()
181       << " to "
182       << target.str()
183       << std::endl);
184
185     return false;
186     }
187
188   return true;
189 }
190
191 //----------------------------------------------------------------------
192 bool cmCPackDragNDropGenerator::RunCommand(cmOStringStream& command,
193   std::string* output)
194 {
195   int exit_code = 1;
196
197   bool result = cmSystemTools::RunSingleCommand(
198     command.str().c_str(),
199     output,
200     &exit_code,
201     0,
202     this->GeneratorVerbose,
203     0);
204
205   if(!result || exit_code)
206     {
207     cmCPackLogger(cmCPackLog::LOG_ERROR,
208       "Error executing: "
209       << command.str()
210       << std::endl);
211
212     return false;
213     }
214
215   return true;
216 }
217
218 //----------------------------------------------------------------------
219 int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
220                                          const std::string& output_file)
221 {
222   // Get optional arguments ...
223   const std::string cpack_package_icon = this->GetOption("CPACK_PACKAGE_ICON")
224     ? this->GetOption("CPACK_PACKAGE_ICON") : "";
225
226   const std::string cpack_dmg_volume_name =
227     this->GetOption("CPACK_DMG_VOLUME_NAME")
228     ? this->GetOption("CPACK_DMG_VOLUME_NAME")
229         : this->GetOption("CPACK_PACKAGE_FILE_NAME");
230
231   const std::string cpack_dmg_format =
232     this->GetOption("CPACK_DMG_FORMAT")
233     ? this->GetOption("CPACK_DMG_FORMAT") : "UDZO";
234
235   // Get optional arguments ...
236   std::string cpack_license_file =
237     this->GetOption("CPACK_RESOURCE_FILE_LICENSE") ?
238     this->GetOption("CPACK_RESOURCE_FILE_LICENSE") : "";
239
240   const std::string cpack_dmg_background_image =
241     this->GetOption("CPACK_DMG_BACKGROUND_IMAGE")
242     ? this->GetOption("CPACK_DMG_BACKGROUND_IMAGE") : "";
243
244   const std::string cpack_dmg_ds_store =
245     this->GetOption("CPACK_DMG_DS_STORE")
246     ? this->GetOption("CPACK_DMG_DS_STORE") : "";
247
248   // only put license on dmg if is user provided
249   if(!cpack_license_file.empty() &&
250       cpack_license_file.find("CPack.GenericLicense.txt") != std::string::npos)
251   {
252     cpack_license_file = "";
253   }
254
255   // The staging directory contains everything that will end-up inside the
256   // final disk image ...
257   cmOStringStream staging;
258   staging << src_dir;
259
260   // Add a symlink to /Applications so users can drag-and-drop the bundle
261   // into it
262   cmOStringStream application_link;
263   application_link << staging.str() << "/Applications";
264   cmSystemTools::CreateSymlink("/Applications",
265     application_link.str().c_str());
266
267   // Optionally add a custom volume icon ...
268   if(!cpack_package_icon.empty())
269     {
270     cmOStringStream package_icon_source;
271     package_icon_source << cpack_package_icon;
272
273     cmOStringStream package_icon_destination;
274     package_icon_destination << staging.str() << "/.VolumeIcon.icns";
275
276     if(!this->CopyFile(package_icon_source, package_icon_destination))
277       {
278       cmCPackLogger(cmCPackLog::LOG_ERROR,
279         "Error copying disk volume icon.  "
280                     "Check the value of CPACK_PACKAGE_ICON."
281         << std::endl);
282
283       return 0;
284       }
285     }
286
287   // Optionally add a custom .DS_Store file
288   // (e.g. for setting background/layout) ...
289   if(!cpack_dmg_ds_store.empty())
290     {
291     cmOStringStream package_settings_source;
292     package_settings_source << cpack_dmg_ds_store;
293
294     cmOStringStream package_settings_destination;
295     package_settings_destination << staging.str() << "/.DS_Store";
296
297     if(!this->CopyFile(package_settings_source, package_settings_destination))
298       {
299       cmCPackLogger(cmCPackLog::LOG_ERROR,
300         "Error copying disk volume settings file.  "
301                     "Check the value of CPACK_DMG_DS_STORE."
302         << std::endl);
303
304       return 0;
305       }
306     }
307
308   // Optionally add a custom background image ...
309   if(!cpack_dmg_background_image.empty())
310     {
311     cmOStringStream package_background_source;
312     package_background_source << cpack_dmg_background_image;
313
314     cmOStringStream package_background_destination;
315     package_background_destination << staging.str() << "/background.png";
316
317     if(!this->CopyFile(package_background_source,
318         package_background_destination))
319       {
320       cmCPackLogger(cmCPackLog::LOG_ERROR,
321         "Error copying disk volume background image.  "
322                     "Check the value of CPACK_DMG_BACKGROUND_IMAGE."
323         << std::endl);
324
325       return 0;
326       }
327
328     cmOStringStream temp_background_hiding_command;
329     temp_background_hiding_command << this->GetOption("CPACK_COMMAND_SETFILE");
330     temp_background_hiding_command << " -a V \"";
331     temp_background_hiding_command << package_background_destination.str();
332     temp_background_hiding_command << "\"";
333
334     if(!this->RunCommand(temp_background_hiding_command))
335       {
336         cmCPackLogger(cmCPackLog::LOG_ERROR,
337           "Error setting attributes on disk volume background image."
338           << std::endl);
339
340       return 0;
341       }
342     }
343
344   // Create a temporary read-write disk image ...
345   std::string temp_image = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
346   temp_image += "/temp.dmg";
347
348   cmOStringStream temp_image_command;
349   temp_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
350   temp_image_command << " create";
351   temp_image_command << " -ov";
352   temp_image_command << " -srcfolder \"" << staging.str() << "\"";
353   temp_image_command << " -volname \""
354     << cpack_dmg_volume_name << "\"";
355   temp_image_command << " -format UDRW";
356   temp_image_command << " \"" << temp_image << "\"";
357
358   if(!this->RunCommand(temp_image_command))
359     {
360       cmCPackLogger(cmCPackLog::LOG_ERROR,
361         "Error generating temporary disk image."
362         << std::endl);
363
364     return 0;
365     }
366
367   // Optionally set the custom icon flag for the image ...
368   if(!cpack_package_icon.empty())
369     {
370     cmOStringStream temp_mount;
371
372     cmOStringStream attach_command;
373     attach_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
374     attach_command << " attach";
375     attach_command << " \"" << temp_image << "\"";
376
377     std::string attach_output;
378     if(!this->RunCommand(attach_command, &attach_output))
379       {
380       cmCPackLogger(cmCPackLog::LOG_ERROR,
381         "Error attaching temporary disk image."
382         << std::endl);
383
384       return 0;
385       }
386
387     cmsys::RegularExpression mountpoint_regex(".*(/Volumes/[^\n]+)\n.*");
388     mountpoint_regex.find(attach_output.c_str());
389     temp_mount << mountpoint_regex.match(1);
390
391     cmOStringStream setfile_command;
392     setfile_command << this->GetOption("CPACK_COMMAND_SETFILE");
393     setfile_command << " -a C";
394     setfile_command << " \"" << temp_mount.str() << "\"";
395
396     if(!this->RunCommand(setfile_command))
397       {
398       cmCPackLogger(cmCPackLog::LOG_ERROR,
399         "Error assigning custom icon to temporary disk image."
400         << std::endl);
401
402       return 0;
403       }
404
405     cmOStringStream detach_command;
406     detach_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
407     detach_command << " detach";
408     detach_command << " \"" << temp_mount.str() << "\"";
409
410     if(!this->RunCommand(detach_command))
411       {
412       cmCPackLogger(cmCPackLog::LOG_ERROR,
413         "Error detaching temporary disk image."
414         << std::endl);
415
416       return 0;
417       }
418     }
419
420   if(!cpack_license_file.empty())
421   {
422     std::string sla_r = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
423     sla_r += "/sla.r";
424
425     std::ifstream ifs;
426     ifs.open(cpack_license_file.c_str());
427     if(ifs.is_open())
428     {
429       cmGeneratedFileStream osf(sla_r.c_str());
430       osf << "#include <CoreServices/CoreServices.r>\n\n";
431       osf << SLAHeader;
432       osf << "\n";
433       osf << "data 'TEXT' (5002, \"English\") {\n";
434       while(ifs.good())
435       {
436         std::string line;
437         std::getline(ifs, line);
438         // escape quotes
439         std::string::size_type pos = line.find('\"');
440         while(pos != std::string::npos)
441         {
442           line.replace(pos, 1, "\\\"");
443           pos = line.find('\"', pos+2);
444         }
445         // break up long lines to avoid Rez errors
446         std::vector<std::string> lines;
447         const size_t max_line_length = 512;
448         for(size_t i=0; i<line.size(); i+= max_line_length)
449           {
450           int line_length = max_line_length;
451           if(i+max_line_length > line.size())
452             line_length = line.size()-i;
453           lines.push_back(line.substr(i, line_length));
454           }
455
456         for(size_t i=0; i<lines.size(); i++)
457           {
458           osf << "        \"" << lines[i] << "\"\n";
459           }
460         osf << "        \"\\n\"\n";
461       }
462       osf << "};\n";
463       osf << "\n";
464       osf << SLASTREnglish;
465       ifs.close();
466       osf.close();
467     }
468
469     // convert to UDCO
470     std::string temp_udco = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
471     temp_udco += "/temp-udco.dmg";
472
473     cmOStringStream udco_image_command;
474     udco_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
475     udco_image_command << " convert \"" << temp_image << "\"";
476     udco_image_command << " -format UDCO";
477     udco_image_command << " -o \"" << temp_udco << "\"";
478
479     std::string error;
480     if(!this->RunCommand(udco_image_command, &error))
481       {
482       cmCPackLogger(cmCPackLog::LOG_ERROR,
483         "Error converting to UDCO dmg for adding SLA." << std::endl
484         << error
485         << std::endl);
486       return 0;
487       }
488
489     // unflatten dmg
490     cmOStringStream unflatten_command;
491     unflatten_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
492     unflatten_command << " unflatten ";
493     unflatten_command << "\"" << temp_udco << "\"";
494
495     if(!this->RunCommand(unflatten_command, &error))
496       {
497       cmCPackLogger(cmCPackLog::LOG_ERROR,
498         "Error unflattening dmg for adding SLA." << std::endl
499         << error
500         << std::endl);
501       return 0;
502       }
503
504     // Rez the SLA
505     cmOStringStream embed_sla_command;
506     embed_sla_command << this->GetOption("CPACK_COMMAND_REZ");
507     embed_sla_command << " \"" << sla_r << "\"";
508     embed_sla_command << " -a -o ";
509     embed_sla_command << "\"" << temp_udco << "\"";
510
511     if(!this->RunCommand(embed_sla_command, &error))
512       {
513       cmCPackLogger(cmCPackLog::LOG_ERROR,
514         "Error adding SLA." << std::endl
515         << error
516         << std::endl);
517       return 0;
518       }
519
520     // flatten dmg
521     cmOStringStream flatten_command;
522     flatten_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
523     flatten_command << " flatten ";
524     flatten_command << "\"" << temp_udco << "\"";
525
526     if(!this->RunCommand(flatten_command, &error))
527       {
528       cmCPackLogger(cmCPackLog::LOG_ERROR,
529         "Error flattening dmg for adding SLA." << std::endl
530         << error
531         << std::endl);
532       return 0;
533       }
534
535     temp_image = temp_udco;
536   }
537
538
539   // Create the final compressed read-only disk image ...
540   cmOStringStream final_image_command;
541   final_image_command << this->GetOption("CPACK_COMMAND_HDIUTIL");
542   final_image_command << " convert \"" << temp_image << "\"";
543   final_image_command << " -format ";
544   final_image_command << cpack_dmg_format;
545   final_image_command << " -imagekey";
546   final_image_command << " zlib-level=9";
547   final_image_command << " -o \"" << output_file << "\"";
548
549   if(!this->RunCommand(final_image_command))
550     {
551     cmCPackLogger(cmCPackLog::LOG_ERROR,
552       "Error compressing disk image."
553       << std::endl);
554
555     return 0;
556     }
557
558   return 1;
559 }
560
561 bool cmCPackDragNDropGenerator::SupportsComponentInstallation() const
562 {
563   return true;
564 }
565
566 std::string
567 cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
568                            const std::string& componentName)
569 {
570   // we want to group components together that go in the same dmg package
571   std::string package_file_name = this->GetOption("CPACK_PACKAGE_FILE_NAME");
572
573   // we have 3 mutually exclusive modes to work in
574   // 1. all components in one package
575   // 2. each group goes in its own package with left over
576   //    components in their own package
577   // 3. ignore groups - if grouping is defined, it is ignored
578   //    and each component goes in its own package
579
580   if(this->componentPackageMethod == ONE_PACKAGE)
581     {
582     return "ALL_IN_ONE";
583     }
584
585   if(this->componentPackageMethod == ONE_PACKAGE_PER_GROUP)
586     {
587     // We have to find the name of the COMPONENT GROUP
588     // the current COMPONENT belongs to.
589     std::string groupVar = "CPACK_COMPONENT_" +
590                          cmSystemTools::UpperCase(componentName) + "_GROUP";
591     const char* _groupName = GetOption(groupVar.c_str());
592     if (_groupName)
593       {
594       std::string groupName = _groupName;
595
596       groupName = GetComponentPackageFileName(package_file_name,
597                                               groupName, true);
598       return groupName;
599       }
600     }
601
602   return GetComponentPackageFileName(package_file_name, componentName, false);
603 }