Bug 320155: create symlinks dmg files (build tool part); r=mento, blocking-firefox3=beltzner
authormnyromyr@tprac.de
Wed, 12 Dec 2007 09:35:45 -0800
changeset 8962 3eb11245684c7f412e17b1e9b84aa4f30ff214eb
parent 8961 7a7cc68f1fccdb68699432de4e5c0bdad01d562d
child 8963 75305da4427c7a90518c823158ca3f129bd23978
push idunknown
push userunknown
push dateunknown
reviewersmento
bugs320155
milestone1.9b3pre
Bug 320155: create symlinks dmg files (build tool part); r=mento, blocking-firefox3=beltzner
build/package/mac_osx/pkg-dmg
--- a/build/package/mac_osx/pkg-dmg
+++ b/build/package/mac_osx/pkg-dmg
@@ -49,16 +49,17 @@ B<pkg-dmg> - Mac OS X disk image (.dmg) 
 B<pkg-dmg>
 B<--source> I<source-folder>
 B<--target> I<target-image>
 [B<--format> I<format>]
 [B<--volname> I<volume-name>]
 [B<--tempdir> I<temp-dir>]
 [B<--mkdir> I<directory>]
 [B<--copy> I<source>[:I<dest>]]
+[B<--symlink> I<source>[:I<dest>]]
 [B<--license> I<file>]
 [B<--resource> I<file>]
 [B<--icon> I<icns-file>]
 [B<--attribute> I<a>:I<file>[:I<file>...]
 [B<--idme>]
 [B<--sourcefile>]
 [B<--verbosity> I<level>]
 [B<--dry-run>]
@@ -129,16 +130,24 @@ specified, I<source> is copied to the lo
 otherwise, I<source> is copied to the root of the new volume.  B<--copy>
 provides a way to package up a I<source-folder> by adding files to it
 without modifying the original I<source-folder>.  B<--copy> may appear
 multiple times.
 
 This option is useful for adding .DS_Store files and window backgrounds
 to disk images.
 
+=item B<--symlink> I<source>[:I<dest>]
+
+Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink
+destinations are interpreted as "like the source path, but inside the dmg"
+
+This option is useful for adding symlinks to external resources,
+e.g. to /Applications.
+
 =item B<--license> I<file>
 
 A plain text file containing a license agreement to be displayed before
 the disk image is mounted.  English is the only supported language.  To
 include license agreements in other languages, in multiple languages,
 or to use formatted text, prepare a resource and use L<--resource>.
 
 =item B<--resource> I<file>
@@ -232,16 +241,17 @@ is to create images which consume a mini
 
 =head1 EXAMPLE
 
 pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg
   --sourcefile --volname DeerPark --icon ~/DeerPark.icns
   --mkdir /.background
   --copy DeerParkBackground.png:/.background/background.png
   --copy DeerParkDSStore:/.DS_Store
+  --symlink /Applications:"/Drag to here"
 
 =head1 REQUIREMENTS
 
 I<pkg-dmg> has been tested with Mac OS X releases 10.2 ("Jaguar")
 through 10.4 ("Tiger").  Certain adjustments to behavior are made
 depending on the host system's release.  Mac OS X 10.3 ("Panther") or
 later are recommended.
 
@@ -267,16 +277,17 @@ use Getopt::Long;
 sub argumentEscape(@);
 sub cleanupDie($);
 sub command(@);
 sub commandInternal($@);
 sub commandInternalVerbosity($$@);
 sub commandOutput(@);
 sub commandOutputVerbosity($@);
 sub commandVerbosity($@);
+sub copyFiles($@);
 sub diskImageMaker($$$$$$$$);
 sub giveExtension($$);
 sub hdidMountImage($@);
 sub isFormatCompressed($);
 sub licenseMaker($$);
 sub pathSplit($);
 sub setAttributes($@);
 sub trapSignal($);
@@ -402,19 +413,19 @@ else {
   # have a chance to make a difference.
   #
   # Now, if someone wanted to document some of these private formats...
   print STDERR ($0.": warning, not running on Mac OS X, ".
    "this could be interesting.\n");
 }
 
 # Non-global variables used in Getopt
-my(@attributes, @copyFiles, $iconFile, $idme, $licenseFile, @makeDirs,
- $outputFormat, @resourceFiles, $sourceFile, $sourceFolder, $targetImage,
- $tempDir, $volumeName);
+my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile,
+ @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder,
+ $targetImage, $tempDir, $volumeName);
 
 # --format
 $outputFormat = 'UDZO';
 
 # --idme
 $idme = 0;
 
 # --sourcefile
@@ -431,16 +442,17 @@ delete $ENV{'NEXT_ROOT'};
 Getopt::Long::Configure('pass_through');
 GetOptions('source=s'    => \$sourceFolder,
            'target=s'    => \$targetImage,
            'volname=s'   => \$volumeName,
            'format=s'    => \$outputFormat,
            'tempdir=s'   => \$tempDir,
            'mkdir=s'     => \@makeDirs,
            'copy=s'      => \@copyFiles,
+           'symlink=s'   => \@createSymlinks,
            'license=s'   => \$licenseFile,
            'resource=s'  => \@resourceFiles,
            'icon=s'      => \$iconFile,
            'attribute=s' => \@attributes,
            'idme'        => \$idme,
            'sourcefile'  => \$sourceFile,
            'verbosity=i' => \$gVerbosity,
            'dry-run'     => \$gDryRun,
@@ -569,34 +581,19 @@ if(@makeDirs) {
       push(@tempDirsToMake, $tempRoot.'/'.$makeDir);
     }
   }
   if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) {
     cleanupDie('mkdir failed');
   }
 }
 
-my($copyFile);
-foreach $copyFile (@copyFiles) {
-  my($copySource, $copyDestination);
-  ($copySource, $copyDestination) = split(/:/, $copyFile);
-  if(!defined($copyDestination)) {
-    $copyDestination = $tempRoot;
-  }
-  elsif($copyDestination =~ /^\//) {
-    $copyDestination = $tempRoot.$copyDestination;
-  }
-  else {
-    $copyDestination = $tempRoot.'/'.$copyDestination;
-  }
-  if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links',
-   $copySource, $copyDestination) != 0) {
-    cleanupDie('rsync failed for item copy');
-  }
-}
+# copy files and/or create symlinks
+copyFiles($tempRoot, 'copy', @copyFiles);
+copyFiles($tempRoot, 'symlink', @createSymlinks);
 
 if($gConfig{'create_directly'}) {
   # If create_directly is false, the contents will be rsynced into a
   # disk image and they would lose their attributes.
   setAttributes($tempRoot, @attributes);
 }
 
 if(defined($iconFile)) {
@@ -725,47 +722,61 @@ sub cleanupDie($) {
 sub command(@) {
   my(@arguments);
   @arguments = @_;
   return commandVerbosity($gVerbosity,@arguments);
 }
 
 # commandInternal($command, @arguments)
 #
-# Removes the files specified by @arguments with a verbosity level specified
-# by $gVerbosity.
+# Runs the specified internal command at the verbosity level defined by
+# $gVerbosity.
+# Returns zero(!) on failure, because commandInternal is supposed to be a
+# direct replacement for the Perl system call wrappers, which, unlike shell
+# commands and C equivalent system calls, return true (instead of 0) to
+# indicate success.
 sub commandInternal($@) {
   my(@arguments, $command);
   ($command, @arguments) = @_;
   return commandInternalVerbosity($gVerbosity, $command, @arguments);
 }
 
 # commandInternalVerbosity($verbosity, $command, @arguments)
 #
 # Run an internal command, printing a bogus command invocation message if
 # $verbosity is true.
 #
 # If $command is unlink:
 # Removes the files specified by @arguments.  Wraps unlink.
 #
-# If $command is mkdir:
-# Creates the directory specified by @arguments, with an optional mask
-# argument, wrapping mkdir.
+# If $command is symlink:
+# Creates the symlink specified by @arguments. Wraps symlink.
 sub commandInternalVerbosity($$@) {
   my(@arguments, $command, $verbosity);
   ($verbosity, $command, @arguments) = @_;
   if($command eq 'unlink') {
     if($verbosity || $gDryRun) {
       print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n");
     }
     if($gDryRun) {
       return $#arguments+1;
     }
     return unlink(@arguments);
   }
+  elsif($command eq 'symlink') {
+    if($verbosity || $gDryRun) {
+      print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n");
+    }
+    if($gDryRun) {
+      return 1;
+    }
+    my($source, $target);
+    ($source, $target) = @arguments;
+    return symlink($source, $target);
+  }
 }
 
 # commandOutput(@arguments)
 #
 # Runs the specified command at the verbosity level defined by $gVerbosity.
 # Output is returned in an array of lines.  undef is returned on failure.
 # The exit status is available in $?.
 sub commandOutput(@) {
@@ -847,16 +858,59 @@ sub commandVerbosity($@) {
   my(@arguments, $verbosity);
   ($verbosity, @arguments) = @_;
   if(!defined(commandOutputVerbosity($verbosity, @arguments))) {
     return -1;
   }
   return $?;
 }
 
+# copyFiles($tempRoot, $method, @arguments)
+#
+# Copies files or create symlinks in the disk image.
+# See --copy and --symlink descriptions for details.
+# If $method is 'copy', @arguments are interpreted as source:target, if $method
+# is 'symlink', @arguments are interpreted as symlink:target.
+sub copyFiles($@) {
+  my(@fileList, $method, $tempRoot);
+  ($tempRoot, $method, @fileList) = @_;
+  my($file, $isSymlink);
+  $isSymlink = ($method eq 'symlink');
+  foreach $file (@fileList) {
+    my($source, $target);
+    ($source, $target) = split(/:/, $file);
+    if(!defined($target) and $isSymlink) {
+      # empty symlink targets would result in an invalid target and fail,
+      # but they shall be interpreted as "like source path, but inside dmg"
+      $target = $source;
+    }
+    if(!defined($target)) {
+      $target = $tempRoot;
+    }
+    elsif($target =~ /^\//) {
+      $target = $tempRoot.$target;
+    }
+    else {
+      $target = $tempRoot.'/'.$target;
+    }
+
+    my($success);
+    if($isSymlink) {
+      $success = commandInternal('symlink', $source, $target);
+    }
+    else {
+      $success = !command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links',
+                          $source, $target);
+    }
+    if(!$success) {
+      cleanupDie('copyFiles failed for method '.$method);
+    }
+  }
+}
+
 # diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount,
 #  $baseName, $setRootIcon)
 #
 # Creates a disk image in $destination of format $format corresponding to the
 # source directory $source.  $name is the volume name.  $tempDir is a good
 # place to write temporary files, which should be empty (aside from the other
 # things that this script might create there, like stage and mount).
 # $tempMount is a mount point for temporary disk images.  $baseName is the
@@ -1443,23 +1497,24 @@ sub trapSignal($) {
   ($signalName) = @_;
   cleanupDie('exiting on SIG'.$signalName);
 }
 
 sub usage() {
   print STDERR (
 "usage: pkg-dmg --source <source-folder>\n".
 "               --target <target-image>\n".
-"              [--format <format>]        (default: UDZO)\n".
-"              [--volname <volume-name>]  (default: same name as source)\n".
-"              [--tempdir <temp-dir>]     (default: same dir as target)\n".
-"              [--mkdir <directory>]      (make directory in image)\n".
-"              [--copy <source>[:<dest>]] (extra files to add)\n".
-"              [--license <file>]         (plain text license agreement)\n".
-"              [--resource <file>]        (flat .r files to merge)\n".
-"              [--icon <icns-file>]       (volume icon)\n".
-"              [--attribute <a>:<file>]   (set file attributes)\n".
-"              [--idme]                   (make an Internet-enabled image)\n".
-"              [--sourcefile]             (treat --source as a file)\n".
-"              [--verbosity <level>]      (0, 1, 2; default=2)\n".
-"              [--dry-run]                (print what would be done)\n");
+"              [--format <format>]           (default: UDZO)\n".
+"              [--volname <volume-name>]     (default: same name as source)\n".
+"              [--tempdir <temp-dir>]        (default: same dir as target)\n".
+"              [--mkdir <directory>]         (make directory in image)\n".
+"              [--copy <source>[:<dest>]]    (extra files to add)\n".
+"              [--symlink <source>[:<dest>]] (extra symlinks to add)\n".
+"              [--license <file>]            (plain text license agreement)\n".
+"              [--resource <file>]           (flat .r files to merge)\n".
+"              [--icon <icns-file>]          (volume icon)\n".
+"              [--attribute <a>:<file>]      (set file attributes)\n".
+"              [--idme]                      (make Internet-enabled image)\n".
+"              [--sourcefile]                (treat --source as a file)\n".
+"              [--verbosity <level>]         (0, 1, 2; default=2)\n".
+"              [--dry-run]                   (print what would be done)\n");
   return;
 }