~ubuntu-branches/ubuntu/oneiric/enigmail/oneiric-updates

« back to all changes in this revision

Viewing changes to build/macosx/universal/unify

  • Committer: Bazaar Package Importer
  • Author(s): Alexander Sack
  • Date: 2010-04-10 01:42:24 UTC
  • Revision ID: james.westby@ubuntu.com-20100410014224-fbq9ui5x3b0h2t36
Tags: 2:1.0.1-0ubuntu1
* First releaase of enigmail 1.0.1 for tbird/icedove 3
  (LP: #527138)
* redo packaging from scratch 
  + add debian/make-orig target that uses xulrunner provided
    buildsystem + enigmail tarball to produce a proper orig.tar.gz
  + use debhelper 7 with mozilla-devscripts
  + use debian source format 3.0 (quilt)
  + patch enigmail to use frozen API only
    - add debian/patches/frozen_api.diff
  + patch build system to not link against -lxul - which isnt
    available for sdks produced by all-static apps like tbird
    - add debian/patches/build_system_dont_link_libxul.diff
  + add minimal build-depends to control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
# ***** BEGIN LICENSE BLOCK *****
 
3
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
4
#
 
5
# The contents of this file are subject to the Mozilla Public License Version
 
6
# 1.1 (the "License"); you may not use this file except in compliance with
 
7
# the License. You may obtain a copy of the License at
 
8
# http://www.mozilla.org/MPL/
 
9
#
 
10
# Software distributed under the License is distributed on an "AS IS" basis,
 
11
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
12
# for the specific language governing rights and limitations under the
 
13
# License.
 
14
#
 
15
# The Original Code is the Mozilla Mac OS X Universal Binary Packaging System
 
16
#
 
17
# The Initial Developer of the Original Code is Google Inc.
 
18
# Portions created by the Initial Developer are Copyright (C) 2006
 
19
# the Initial Developer. All Rights Reserved.
 
20
#
 
21
# Contributor(s):
 
22
#  Mark Mentovai <mark@moxienet.com> (Original Author)
 
23
#
 
24
# Alternatively, the contents of this file may be used under the terms of
 
25
# either the GNU General Public License Version 2 or later (the "GPL"), or
 
26
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
27
# in which case the provisions of the GPL or the LGPL are applicable instead
 
28
# of those above. If you wish to allow use of your version of this file only
 
29
# under the terms of either the GPL or the LGPL, and not to allow others to
 
30
# use your version of this file under the terms of the MPL, indicate your
 
31
# decision by deleting the provisions above and replace them with the notice
 
32
# and other provisions required by the GPL or the LGPL. If you do not delete
 
33
# the provisions above, a recipient may use your version of this file under
 
34
# the terms of any one of the MPL, the GPL or the LGPL.
 
35
#
 
36
# ***** END LICENSE BLOCK *****
 
37
 
 
38
use strict;
 
39
use warnings;
 
40
 
 
41
=pod
 
42
 
 
43
=head1 NAME
 
44
 
 
45
B<unify> - Mac OS X universal binary packager
 
46
 
 
47
=head1 SYNOPSIS
 
48
 
 
49
B<unify>
 
50
I<ppc-path>
 
51
I<x86-path>
 
52
I<universal-path>
 
53
[B<--dry-run>]
 
54
[B<--only-one> I<action>]
 
55
[B<--verbosity> I<level>]
 
56
[B<--unify-with-sort> I<regex>]
 
57
 
 
58
=head1 DESCRIPTION
 
59
 
 
60
I<unify> merges any two architecture-specific files or directory trees
 
61
into a single file or tree suitable for use on either architecture as a
 
62
"fat" or "universal binary."
 
63
 
 
64
Architecture-specific Mach-O files will be merged into fat Mach-O files
 
65
using L<lipo(1)>.  Non-Mach-O files in the architecture-specific trees
 
66
are compared to ensure that they are equivalent before copying.  Symbolic
 
67
links are permitted in the architecture-specific trees and will cause
 
68
identical links to be created in the merged tree, provided that the source
 
69
links have identical targets.  Directories are processed recursively.
 
70
 
 
71
If the architecture-specific source trees contain zip archives (including
 
72
jar files) that are not identical according to a byte-for-byte check, they
 
73
are still assumed to be equivalent if both archives contain exactly the
 
74
same members with identical checksums and sizes.
 
75
 
 
76
Behavior when one architecture-specific tree contains files that the other
 
77
does not is controlled by the B<--only-one> option.
 
78
 
 
79
If Mach-O files cannot be merged using L<lipo(1)>, zip archives are not
 
80
equivalent, regular files are not identical, or any other error occurs,
 
81
B<unify> will fail with an exit status of 1.  Diagnostic messages are
 
82
typically printed to stderr; this behavior can be controlled with the
 
83
B<--verbosity> option.
 
84
 
 
85
=head1 OPTIONS
 
86
 
 
87
=over 5
 
88
 
 
89
=item I<ppc-path>
 
90
 
 
91
=item I<x86-path>
 
92
 
 
93
The paths to directory trees containing PowerPC and x86 builds,
 
94
respectively.  I<ppc-path> and I<x86-path> are permitted to contain files
 
95
that are already "fat," and only the appropriate architecture's images will
 
96
be used.
 
97
 
 
98
I<ppc-path> and I<x86-path> are also permitted to both be files, in which
 
99
case B<unify> operates solely on those files, and produces an appropriate
 
100
merged file at I<target-path>.
 
101
 
 
102
=item I<target-path>
 
103
 
 
104
The path to the merged file or directory tree.  This path will be created,
 
105
and it must not exist prior to running B<unify>.
 
106
 
 
107
=item B<--dry-run>
 
108
 
 
109
When specified, the commands that would be executed are printed, without
 
110
actually executing them.  Note that B<--dry-run> and the equivalent
 
111
B<--verbosity> level during "wet" runs may print equivalent commands when
 
112
no commands are in fact executed: certain operations are handled internally
 
113
within B<unify>, and an approximation of a command that performs a similar
 
114
task is printed.
 
115
 
 
116
=item B<--only-one> I<action>
 
117
 
 
118
Controls handling of files that are only present in one of the two source
 
119
trees.  I<action> may be:
 
120
  skip - These files are skipped.
 
121
  copy - These files are copied from the tree in which they exist.
 
122
  fail - When this condition occurs, it is treated as an error.
 
123
 
 
124
The default I<action> is copy.
 
125
 
 
126
=item B<--verbosity> I<level>
 
127
 
 
128
Adjusts the level of loudness of B<unify>.  The possible values for
 
129
I<level> are:
 
130
  0 - B<unify> never prints anything.
 
131
      (Other programs that B<unify> calls may still print messages.)
 
132
  1 - Fatal error messages are printed to stderr.
 
133
  2 - Nonfatal warnings are printed to stderr.
 
134
  3 - Commands are printed to stdout as they are executed.
 
135
 
 
136
The default I<level> is 2.
 
137
 
 
138
=item B<--unify-with-sort> I<regex>
 
139
 
 
140
Allows merging files matching I<regex> that differ only by the ordering
 
141
of the lines contained within them. The unified file will have its contents
 
142
sorted. This option may be given multiple times to specify multiple
 
143
regexes for matching files.
 
144
 
 
145
=back
 
146
 
 
147
=head1 EXAMPLES
 
148
 
 
149
=over 5
 
150
 
 
151
=item Create a universal .app bundle from two architecture-specific .app
 
152
bundles:
 
153
 
 
154
unify --only-one copy ppc/dist/firefox/Firefox.app
 
155
  x86/dist/firefox/Firefox.app universal/Firefox.app
 
156
  --verbosity 3
 
157
 
 
158
=item Merge two identical architecture-specific trees:
 
159
 
 
160
unify --only-one fail /usr/local /nfs/x86/usr/local
 
161
  /tmp/usrlocal.fat
 
162
 
 
163
=back
 
164
 
 
165
=head1 REQUIREMENTS
 
166
 
 
167
The only esoteric requirement of B<unify> is that the L<lipo(1)> command
 
168
be available.  It is present on Mac OS X systems at least as early as
 
169
10.3.9, and probably earlier.  Mac OS X 10.4 ("Tiger") or later are
 
170
recommended.
 
171
 
 
172
=head1 LICENSE
 
173
 
 
174
MPL 1.1/GPL 2.0/LGPL 2.1.  Your choice
 
175
 
 
176
=head1 AUTHOR
 
177
 
 
178
The software was initially written by Mark Mentovai; copyright 2006
 
179
Google Inc.
 
180
 
 
181
=head1 SEE ALSO
 
182
 
 
183
L<cmp(1)>, L<ditto(1)>, L<lipo(1)>
 
184
 
 
185
=cut
 
186
 
 
187
use Archive::Zip(':ERROR_CODES');
 
188
use Errno;
 
189
use Fcntl;
 
190
use File::Compare;
 
191
use File::Copy;
 
192
use Getopt::Long;
 
193
 
 
194
my (%gConfig, $gDryRun, $gOnlyOne, $gVerbosity, @gSortMatches);
 
195
 
 
196
sub argumentEscape(@);
 
197
sub command(@);
 
198
sub compareZipArchives($$);
 
199
sub complain($$@);
 
200
sub copyIfIdentical($$$);
 
201
sub slurp($);
 
202
sub compare_sorted($$);
 
203
sub copy_sorted($$);
 
204
sub copyIfIdenticalWhenSorted($$$);
 
205
sub createUniqueFile($$);
 
206
sub makeUniversal($$$);
 
207
sub makeUniversalDirectory($$$);
 
208
sub makeUniversalInternal($$$$);
 
209
sub makeUniversalFile($$$);
 
210
sub usage();
 
211
sub readZipCRCs($);
 
212
 
 
213
{
 
214
  package FileAttrCache;
 
215
 
 
216
  sub new($$);
 
217
 
 
218
  sub isFat($);
 
219
  sub isMachO($);
 
220
  sub isZip($);
 
221
  sub lIsDir($);
 
222
  sub lIsExecutable($);
 
223
  sub lIsRegularFile($);
 
224
  sub lIsSymLink($);
 
225
  sub lstat($);
 
226
  sub lstatMode($);
 
227
  sub lstatType($);
 
228
  sub magic($);
 
229
  sub magic2($);
 
230
  sub path($);
 
231
  sub stat($);
 
232
  sub statSize($);
 
233
}
 
234
 
 
235
%gConfig = (
 
236
  'cmd_lipo' => 'lipo',
 
237
  'cmd_rm'   => 'rm',
 
238
);
 
239
 
 
240
$gDryRun = 0;
 
241
$gOnlyOne = 'copy';
 
242
$gVerbosity = 2;
 
243
@gSortMatches = ();
 
244
 
 
245
Getopt::Long::Configure('pass_through');
 
246
GetOptions('dry-run'           => \$gDryRun,
 
247
           'only-one=s'        => \$gOnlyOne,
 
248
           'verbosity=i'       => \$gVerbosity,
 
249
           'unify-with-sort=s' => \@gSortMatches,
 
250
           'config=s'          => \%gConfig); # "hidden" option not in usage()
 
251
 
 
252
if (scalar(@ARGV) != 3 || $gVerbosity < 0 || $gVerbosity > 3 ||
 
253
    ($gOnlyOne ne 'skip' && $gOnlyOne ne 'copy' && $gOnlyOne ne 'fail')) {
 
254
  usage();
 
255
  exit(1);
 
256
}
 
257
 
 
258
if (!makeUniversal($ARGV[0],$ARGV[1],$ARGV[2])) {
 
259
  # makeUniversal or something it called will have printed an error.
 
260
  exit(1);
 
261
}
 
262
 
 
263
exit(0);
 
264
 
 
265
# argumentEscape(@arguments)
 
266
#
 
267
# Takes a list of @arguments and makes them shell-safe.
 
268
sub argumentEscape(@) {
 
269
  my (@arguments);
 
270
  @arguments = @_;
 
271
 
 
272
  my ($argument, @argumentsOut);
 
273
  foreach $argument (@arguments) {
 
274
    $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g;
 
275
    push(@argumentsOut, $argument);
 
276
  }
 
277
 
 
278
  return @argumentsOut;
 
279
}
 
280
 
 
281
# command(@arguments)
 
282
#
 
283
# Runs the specified command by calling system(@arguments).  If $gDryRun
 
284
# is true, the command is printed but not executed, and 0 is returned.
 
285
# if $gVerbosity is greater than 1, the command is printed before being
 
286
# executed.  When the command is executed, the system() return value will
 
287
# be returned.  stdout and stderr are left connected for command output.
 
288
sub command(@) {
 
289
  my (@arguments);
 
290
  @arguments = @_;
 
291
  if ($gVerbosity >= 3 || $gDryRun) {
 
292
    print(join(' ', argumentEscape(@arguments))."\n");
 
293
  }
 
294
  if ($gDryRun) {
 
295
    return 0;
 
296
  }
 
297
  return system(@arguments);
 
298
}
 
299
 
 
300
# compareZipArchives($zip1, $zip2)
 
301
#
 
302
# Given two pathnames to zip archives, determines whether or not they are
 
303
# functionally identical.  Returns true if they are, false if they differ in
 
304
# some substantial way, and undef if an error occurs.  If the zip files
 
305
# differ, diagnostic messages are printed indicating how they differ.
 
306
#
 
307
# Zip files will differ if any of the members are different as defined by
 
308
# readZipCRCs, which consider CRCs, sizes, and file types as stored in the
 
309
# file header.  Timestamps are not considered.  Zip files also differ if one
 
310
# file contains members that the other one does not.  $gOnlyOne has no
 
311
# effect on this behavior.
 
312
sub compareZipArchives($$) {
 
313
  my ($zip1, $zip2);
 
314
  ($zip1, $zip2) = @_;
 
315
 
 
316
  my ($CRCHash1, $CRCHash2);
 
317
  if (!defined($CRCHash1 = readZipCRCs($zip1))) {
 
318
    # readZipCRCs printed an error.
 
319
    return undef;
 
320
  }
 
321
  if (!defined($CRCHash2 = readZipCRCs($zip2))) {
 
322
    # readZipCRCs printed an error.
 
323
    return undef;
 
324
  }
 
325
 
 
326
  my (@diffCRCs, @onlyInZip1);
 
327
  @diffCRCs = ();
 
328
  @onlyInZip1 = ();
 
329
 
 
330
  my ($memberName);
 
331
  foreach $memberName (keys(%$CRCHash1)) {
 
332
    if (!exists($$CRCHash2{$memberName})) {
 
333
      # The member is present in $zip1 but not $zip2.
 
334
      push(@onlyInZip1, $memberName);
 
335
    }
 
336
    elsif ($$CRCHash1{$memberName} ne $$CRCHash2{$memberName}) {
 
337
      # The member is present in both archives but its CRC or some other
 
338
      # other critical attribute isn't identical.
 
339
      push(@diffCRCs, $memberName);
 
340
    }
 
341
    delete($$CRCHash2{$memberName});
 
342
  }
 
343
 
 
344
  # If any members remain in %CRCHash2, it's because they're not present
 
345
  # in $zip1.
 
346
  my (@onlyInZip2);
 
347
  @onlyInZip2 = keys(%$CRCHash2);
 
348
 
 
349
  if (scalar(@onlyInZip1) + scalar(@onlyInZip2) + scalar(@diffCRCs)) {
 
350
    complain(1, 'compareZipArchives: zip archives differ:',
 
351
             $zip1,
 
352
             $zip2);
 
353
    if (scalar(@onlyInZip1)) {
 
354
      complain(1, 'compareZipArchives: members only in former:',
 
355
               @onlyInZip1);
 
356
    }
 
357
    if (scalar(@onlyInZip2)) {
 
358
      complain(1, 'compareZipArchives: members only in latter:',
 
359
               @onlyInZip2);
 
360
    }
 
361
    if (scalar(@diffCRCs)) {
 
362
      complain(1, 'compareZipArchives: members differ:',
 
363
               @diffCRCs);
 
364
    }
 
365
    return 0;
 
366
  }
 
367
 
 
368
  return 1;
 
369
}
 
370
 
 
371
# complain($severity, $message, @list)
 
372
#
 
373
# Prints $message to stderr if $gVerbosity allows it for severity level
 
374
# $severity.  @list is a list of words that will be shell-escaped and printed
 
375
# after $message, one per line, intended to be used, for example, to list
 
376
# arguments to a call that failed.
 
377
#
 
378
# Expected severity levels are 1 for hard errors and 2 for non-fatal warnings.
 
379
#
 
380
# Always returns false as a convenience, so callers can return complain's
 
381
# return value when it is used to signal errors.
 
382
sub complain($$@) {
 
383
  my ($severity, $message, @list);
 
384
  ($severity, $message, @list) = @_;
 
385
 
 
386
  if ($gVerbosity >= $severity) {
 
387
    print STDERR ($0.': '.$message."\n");
 
388
 
 
389
    my ($item);
 
390
    while ($item = shift(@list)) {
 
391
      print STDERR ('  '.(argumentEscape($item))[0].
 
392
                    (scalar(@list)?',':'')."\n");
 
393
    }
 
394
  }
 
395
 
 
396
  return 0;
 
397
}
 
398
 
 
399
# copyIfIdentical($source1, $source2, $target)
 
400
#
 
401
# $source1 and $source2 are FileAttrCache objects that are compared, and if
 
402
# identical, copied to path string $target.  The comparison is initially
 
403
# done as a byte-for-byte comparison, but if the files differ and appear to
 
404
# be zip archives, compareZipArchives is called to determine whether
 
405
# files that are not byte-for-byte identical are equivalent archives.
 
406
#
 
407
# Returns true on success, false for files that are not identical or
 
408
# equivalent archives, and undef if an error occurs.
 
409
#
 
410
# One of $source1 and $source2 is permitted to be undef.  In this event,
 
411
# whichever source is defined is copied directly to $target without performing
 
412
# any comparisons.  This enables the $gOnlyOne = 'copy' mode, which is
 
413
# driven by makeUniversalDirectory and makeUniversalInternal.
 
414
sub copyIfIdentical($$$) {
 
415
  my ($source1, $source2, $target);
 
416
  ($source1, $source2, $target) = @_;
 
417
 
 
418
  if (!defined($source1)) {
 
419
    # If there's only one source file, make it the first file.  Order
 
420
    # isn't important here, and this makes it possible to use
 
421
    # defined($source2) as the switch, and to always copy from $source1.
 
422
    $source1 = $source2;
 
423
    $source2 = undef;
 
424
  }
 
425
 
 
426
  if (defined($source2)) {
 
427
    # Only do the comparisons if there are two source files.  If there's
 
428
    # only one source file, skip the comparisons and go straight to the
 
429
    # copy operation.
 
430
    if ($gVerbosity >= 3 || $gDryRun) {
 
431
      print('cmp -s '.
 
432
            join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
 
433
    }
 
434
    my ($comparison);
 
435
    if (!defined($comparison = compare($source1->path(), $source2->path())) ||
 
436
        $comparison == -1) {
 
437
      return complain(1, 'copyIfIdentical: compare: '.$!.' while comparing:',
 
438
                      $source1->path(),
 
439
                      $source2->path());
 
440
    }
 
441
    elsif ($comparison != 0) {
 
442
      my ($zip1, $zip2);
 
443
      if (defined($zip1 = $source1->isZip()) &&
 
444
          defined($zip2 = $source2->isZip()) &&
 
445
          $zip1 && $zip2) {
 
446
        my ($zipComparison);
 
447
        if (!defined($zipComparison = compareZipArchives($source1->path(),
 
448
                                                         $source2->path)) ||
 
449
            !$zipComparison) {
 
450
          # An error occurred or the zip files aren't sufficiently identical.
 
451
          # compareZipArchives will have printed an error message.
 
452
          return 0;
 
453
        }
 
454
        # The zip files were compared successfully, and they both contain
 
455
        # all of the same members, and all of their members' CRCs are
 
456
        # identical.  For the purposes of this script, the zip files can be
 
457
        # treated as identical, so reset $comparison.
 
458
        $comparison = 0;
 
459
      }
 
460
    }
 
461
    if ($comparison != 0) {
 
462
      return complain(1, 'copyIfIdentical: files differ:',
 
463
                      $source1->path(),
 
464
                      $source2->path());
 
465
    }
 
466
  }
 
467
 
 
468
  if ($gVerbosity >= 3 || $gDryRun) {
 
469
    print('cp '.
 
470
          join(' ',argumentEscape($source1->path(), $target))."\n");
 
471
  }
 
472
 
 
473
  if (!$gDryRun) {
 
474
    my ($isExecutable);
 
475
 
 
476
    # Set the execute bits (as allowed by the umask) on the new file if any
 
477
    # execute bit is set on either old file.
 
478
    $isExecutable = $source1->lIsExecutable() ||
 
479
                    (defined($source2) && $source2->lIsExecutable());
 
480
 
 
481
    if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
 
482
      # createUniqueFile printed an error.
 
483
      return 0;
 
484
    }
 
485
 
 
486
    if (!copy($source1->path(), $target)) {
 
487
      complain(1, 'copyIfIdentical: copy: '.$!.' while copying',
 
488
               $source1->path(),
 
489
               $target);
 
490
      unlink($target);
 
491
      return 0;
 
492
    }
 
493
  }
 
494
 
 
495
  return 1;
 
496
}
 
497
 
 
498
# slurp($file)
 
499
#
 
500
# Read the contents of $file into an array and return it.
 
501
# Returns undef on error.
 
502
sub slurp($) {
 
503
  my $file = $_[0];
 
504
  open FILE, $file or return undef;
 
505
  my @lines = <FILE>;
 
506
  close FILE;
 
507
  return @lines;
 
508
}
 
509
 
 
510
# compare_sorted($file1, $file2)
 
511
#
 
512
# Read the contents of both files into arrays, sort the arrays,
 
513
# and then compare the two arrays for equality.
 
514
#
 
515
# Returns 0 if the sorted array contents are equal, or 1 if not.
 
516
# Returns undef on error.
 
517
sub compare_sorted($$) {
 
518
  my ($file1, $file2) = @_;
 
519
  my @lines1 = sort(slurp($file1));
 
520
  my @lines2 = sort(slurp($file2));
 
521
 
 
522
  return undef if !@lines1 || !@lines2;
 
523
  return 1 unless scalar @lines1 == scalar @lines2;
 
524
 
 
525
  for (my $i = 0; $i < scalar @lines1; $i++) {
 
526
    return 1 if $lines1[$i] ne $lines2[$i];
 
527
  }
 
528
  return 0;
 
529
}
 
530
 
 
531
# copy_sorted($source, $destination)
 
532
#
 
533
# $source and $destination are filenames. Read the contents of $source
 
534
# into an array, sort it, and then write the sorted contents to $destination.
 
535
# Returns 1 on success, and undef on failure.
 
536
sub copy_sorted($$) {
 
537
  my ($src, $dest) = @_;
 
538
  my @lines = sort(slurp($src));
 
539
  return undef unless @lines;
 
540
  open FILE, "> $dest" or return undef;
 
541
  print FILE @lines;
 
542
  close FILE;
 
543
  return 1;
 
544
}
 
545
 
 
546
# copyIfIdenticalWhenSorted($source1, $source2, $target)
 
547
#
 
548
# $source1 and $source2 are FileAttrCache objects that are compared, and if
 
549
# identical, copied to path string $target.  The comparison is done by
 
550
# sorting the individual lines within the two files and comparing the results.
 
551
#
 
552
# Returns true on success, false for files that are not equivalent,
 
553
# and undef if an error occurs.
 
554
sub copyIfIdenticalWhenSorted($$$) {
 
555
  my ($source1, $source2, $target);
 
556
  ($source1, $source2, $target) = @_;
 
557
 
 
558
  if ($gVerbosity >= 3 || $gDryRun) {
 
559
    print('cmp -s '.
 
560
          join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
 
561
  }
 
562
  my ($comparison);
 
563
  if (!defined($comparison = compare_sorted($source1->path(),
 
564
                                            $source2->path())) ||
 
565
      $comparison == -1) {
 
566
    return complain(1, 'copyIfIdenticalWhenSorted: compare: '.$!
 
567
                    .' while comparing:',
 
568
                      $source1->path(),
 
569
                      $source2->path());
 
570
  }
 
571
  if ($comparison != 0) {
 
572
    return complain(1, 'copyIfIdenticalWhenSorted: files differ:',
 
573
                    $source1->path(),
 
574
                    $source2->path());
 
575
  }
 
576
 
 
577
  if ($gVerbosity >= 3 || $gDryRun) {
 
578
    print('cp '.
 
579
          join(' ',argumentEscape($source1->path(), $target))."\n");
 
580
  }
 
581
 
 
582
  if (!$gDryRun) {
 
583
    my ($isExecutable);
 
584
 
 
585
    # Set the execute bits (as allowed by the umask) on the new file if any
 
586
    # execute bit is set on either old file.
 
587
    $isExecutable = $source1->lIsExecutable() ||
 
588
                    (defined($source2) && $source2->lIsExecutable());
 
589
 
 
590
    if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
 
591
      # createUniqueFile printed an error.
 
592
      return 0;
 
593
    }
 
594
 
 
595
    if (!copy_sorted($source1->path(), $target)) {
 
596
      complain(1, 'copyIfIdenticalWhenSorted: copy_sorted: '.$!
 
597
               .' while copying',
 
598
               $source1->path(),
 
599
               $target);
 
600
      unlink($target);
 
601
      return 0;
 
602
    }
 
603
  }
 
604
 
 
605
  return 1;
 
606
}
 
607
 
 
608
# createUniqueFile($path, $mode)
 
609
#
 
610
# Creates a new plain empty file at pathname $path, provided it does not
 
611
# yet exist.  $mode is used as the file mode.  The actual file's mode will
 
612
# be modified by the effective umask.  Returns false if the file could
 
613
# not be created, setting $! to the error.  An error message is printed
 
614
# in the event of failure.
 
615
sub createUniqueFile($$) {
 
616
  my ($path, $mode);
 
617
  ($path, $mode) = @_;
 
618
 
 
619
  my ($fh);
 
620
  if (!sysopen($fh, $path, O_WRONLY | O_CREAT | O_EXCL, $mode)) {
 
621
    return complain(1, 'createUniqueFile: open: '.$!.' for:',
 
622
                    $path);
 
623
  }
 
624
  close($fh);
 
625
 
 
626
  return 1;
 
627
}
 
628
 
 
629
# makeUniversal($pathPPC, $pathX86, $pathTarget)
 
630
#
 
631
# The top-level call.  $pathPPC, $pathX86, and $pathTarget are strings
 
632
# identifying the ppc and x86 files or directories to merge and the location
 
633
# to merge them to.  Returns false on failure and true on success.
 
634
sub makeUniversal($$$) {
 
635
  my ($pathTarget, $pathPPC, $pathX86);
 
636
  ($pathPPC, $pathX86, $pathTarget) = @_;
 
637
 
 
638
  my ($filePPC, $fileX86);
 
639
  $filePPC = FileAttrCache->new($pathPPC);
 
640
  $fileX86 = FileAttrCache->new($pathX86);
 
641
 
 
642
  return makeUniversalInternal(1, $filePPC, $fileX86, $pathTarget);
 
643
}
 
644
 
 
645
# makeUniversalDirectory($dirPPC, $dirX86, $dirTarget)
 
646
#
 
647
# This is part of the heart of recursion.  $dirPPC and $dirX86 are
 
648
# FileAttrCache objects designating the source ppc and x86 directories to
 
649
# merge into a universal directory at $dirTarget, a string.  For each file
 
650
# in $dirPPC and $dirX86, makeUniversalInternal is called.
 
651
# makeUniversalInternal will call back into makeUniversalDirectory for
 
652
# directories, thus completing the recursion.  If a failure is encountered
 
653
# in ths function or in makeUniversalInternal or anything that it calls,
 
654
# false is returned, otherwise, true is returned.
 
655
#
 
656
# If there are files present in one source directory but not both, the
 
657
# value of $gOnlyOne controls the behavior.  If $gOnlyOne is 'copy', the
 
658
# single source file is copied into $pathTarget.  If it is 'skip', it is
 
659
# skipped.  If it is 'fail', such files will trigger makeUniversalDirectory
 
660
# to fail.
 
661
#
 
662
# If either source directory is undef, it is treated as having no files.
 
663
# This facilitates deep recursion when entire directories are only present
 
664
# in one source when $gOnlyOne = 'copy'.
 
665
sub makeUniversalDirectory($$$) {
 
666
  my ($dirPPC, $dirX86, $dirTarget);
 
667
  ($dirPPC, $dirX86, $dirTarget) = @_;
 
668
 
 
669
  my ($dh, @filesPPC, @filesX86);
 
670
 
 
671
  @filesPPC = ();
 
672
  if (defined($dirPPC)) {
 
673
    if (!opendir($dh, $dirPPC->path())) {
 
674
      return complain(1, 'makeUniversalDirectory: opendir ppc: '.$!.' for:',
 
675
                      $dirPPC->path());
 
676
    }
 
677
    @filesPPC = readdir($dh);
 
678
    closedir($dh);
 
679
  }
 
680
 
 
681
  @filesX86 = ();
 
682
  if (defined($dirX86)) {
 
683
    if (!opendir($dh, $dirX86->path())) {
 
684
      return complain(1, 'makeUniversalDirectory: opendir x86: '.$!.' for:',
 
685
                      $dirX86->path());
 
686
     }
 
687
    @filesX86 = readdir($dh);
 
688
    closedir($dh);
 
689
  }
 
690
 
 
691
  my (%common, $file, %onlyPPC, %onlyX86);
 
692
 
 
693
  %onlyPPC = ();
 
694
  foreach $file (@filesPPC) {
 
695
    if ($file eq '.' || $file eq '..') {
 
696
      next;
 
697
    }
 
698
    $onlyPPC{$file}=1;
 
699
  }
 
700
 
 
701
  %common = ();
 
702
  %onlyX86 = ();
 
703
  foreach $file (@filesX86) {
 
704
    if ($file eq '.' || $file eq '..') {
 
705
      next;
 
706
    }
 
707
    if ($onlyPPC{$file}) {
 
708
      delete $onlyPPC{$file};
 
709
      $common{$file}=1;
 
710
    }
 
711
    else {
 
712
      $onlyX86{$file}=1;
 
713
    }
 
714
  }
 
715
 
 
716
  # First, handle files common to both.
 
717
  foreach $file (sort(keys(%common))) {
 
718
    if (!makeUniversalInternal(0,
 
719
                               FileAttrCache->new($dirPPC->path().'/'.$file),
 
720
                               FileAttrCache->new($dirX86->path().'/'.$file),
 
721
                               $dirTarget.'/'.$file)) {
 
722
      # makeUniversalInternal will have printed an error.
 
723
      return 0;
 
724
    }
 
725
  }
 
726
 
 
727
  # Handle files found only in a single directory here.  There are three
 
728
  # options, dictated by $gOnlyOne: fail if files are only present in
 
729
  # one directory, skip any files only present in one directory, or copy
 
730
  # these files straight over to the target directory.  In any event,
 
731
  # a message will be printed indicating that the file trees don't match
 
732
  # exactly.
 
733
  if (keys(%onlyPPC)) {
 
734
    complain(($gOnlyOne eq 'fail' ? 1 : 2),
 
735
             ($gOnlyOne ne 'fail' ? 'warning: ' : '').
 
736
             'makeUniversalDirectory: only in ppc '.
 
737
             (argumentEscape($dirPPC->path()))[0].':',
 
738
             argumentEscape(keys(%onlyPPC)));
 
739
  }
 
740
 
 
741
  if (keys(%onlyX86)) {
 
742
    complain(($gOnlyOne eq 'fail' ? 1 : 2),
 
743
             ($gOnlyOne ne 'fail' ? 'warning: ' : '').
 
744
             'makeUniversalDirectory: only in x86 '.
 
745
             (argumentEscape($dirX86->path()))[0].':',
 
746
             argumentEscape(keys(%onlyX86)));
 
747
  }
 
748
 
 
749
  if ($gOnlyOne eq 'fail' && (keys(%onlyPPC) || keys(%onlyX86))) {
 
750
    # Error message(s) printed above.
 
751
    return 0;
 
752
  }
 
753
 
 
754
  if ($gOnlyOne eq 'copy') {
 
755
    foreach $file (sort(keys(%onlyPPC))) {
 
756
      if (!makeUniversalInternal(0,
 
757
                                 FileAttrCache->new($dirPPC->path().'/'.$file),
 
758
                                 undef,
 
759
                                 $dirTarget.'/'.$file)) {
 
760
        # makeUniversalInternal will have printed an error.
 
761
        return 0;
 
762
      }
 
763
    }
 
764
 
 
765
    foreach $file (sort(keys(%onlyX86))) {
 
766
      if (!makeUniversalInternal(0,
 
767
                                 undef,
 
768
                                 FileAttrCache->new($dirX86->path().'/'.$file),
 
769
                                 $dirTarget.'/'.$file)) {
 
770
        # makeUniversalInternal will have printed an error.
 
771
        return 0;
 
772
      }
 
773
    }
 
774
  }
 
775
 
 
776
  return 1;
 
777
}
 
778
 
 
779
# makeUniversalFile($sourcePPC, $sourceX86, $targetPath)
 
780
#
 
781
# Creates a universal file at pathname $targetPath based on a ppc image at
 
782
# $sourcePPC and an x86 image at $sourceX86.  $sourcePPC and $sourceX86 are
 
783
# both FileAttrCache objects.  Returns true on success and false on failure.
 
784
# On failure, diagnostics will be printed to stderr.
 
785
#
 
786
# The source files may be either thin Mach-O images of the appropriate
 
787
# architecture, or fat Mach-O files that contain images of the appropriate
 
788
# architecture.
 
789
#
 
790
# This function wraps the lipo utility, see lipo(1).
 
791
sub makeUniversalFile($$$) {
 
792
  my ($sourcePPC, $sourceX86, $targetPath, @tempThinFiles, $thinPPC, $thinX86);
 
793
  ($sourcePPC, $sourceX86, $targetPath) = @_;
 
794
  $thinPPC = $sourcePPC;
 
795
  $thinX86 = $sourceX86;
 
796
 
 
797
  @tempThinFiles = ();
 
798
 
 
799
  # The source files might already be fat.  They should be thinned out to only
 
800
  # contain a single architecture.
 
801
 
 
802
  my ($isFatPPC, $isFatX86);
 
803
 
 
804
  if(!defined($isFatPPC = $sourcePPC->isFat())) {
 
805
    # isFat printed its own error
 
806
    return 0;
 
807
  }
 
808
  elsif($isFatPPC) {
 
809
    $thinPPC = FileAttrCache->new($targetPath.'.ppc');
 
810
    push(@tempThinFiles, $thinPPC->path());
 
811
    if (command($gConfig{'cmd_lipo'}, '-thin', 'ppc',
 
812
                $sourcePPC->path(), '-output', $thinPPC->path()) != 0) {
 
813
      unlink(@tempThinFiles);
 
814
      return complain(1, 'lipo thin ppc failed for:',
 
815
                      $sourcePPC->path(),
 
816
                      $thinPPC->path());
 
817
    }
 
818
  }
 
819
 
 
820
  if(!defined($isFatX86 = $sourceX86->isFat())) {
 
821
    # isFat printed its own error
 
822
    unlink(@tempThinFiles);
 
823
    return 0;
 
824
  }
 
825
  elsif($isFatX86) {
 
826
    $thinX86 = FileAttrCache->new($targetPath.'.x86');
 
827
    push(@tempThinFiles, $thinX86->path());
 
828
    if (command($gConfig{'cmd_lipo'}, '-thin', 'i386',
 
829
                $sourceX86->path(), '-output', $thinX86->path()) != 0) {
 
830
      unlink(@tempThinFiles);
 
831
      return complain(1, 'lipo thin x86 failed for:',
 
832
                      $sourceX86->path(),
 
833
                      $thinX86->path());
 
834
    }
 
835
  }
 
836
 
 
837
  # The image for each architecture in the fat file will be aligned on
 
838
  # a specific boundary, default 4096 bytes, see lipo(1) -segalign.
 
839
  # Since there's no tail-padding, the fat file will consume the least
 
840
  # space on disk if the image that comes last exceeds the segment size
 
841
  # by the smallest amount.
 
842
  #
 
843
  # This saves an average of 1kB per fat file over the naive approach of
 
844
  # always putting one architecture first: average savings is 2kB per
 
845
  # file, but the naive approach would have gotten it right half of the
 
846
  # time.
 
847
 
 
848
  my ($sizePPC, $sizeX86, $thinPPCForStat, $thinX86ForStat);
 
849
 
 
850
  if (!$gDryRun) {
 
851
    $thinPPCForStat = $thinPPC;
 
852
    $thinX86ForStat = $thinX86;
 
853
  }
 
854
  else {
 
855
    # Normally, fat source files will have been converted into temporary
 
856
    # thin files.  During a dry run, that doesn't happen, so fake it up
 
857
    # a little bit by always using the source file, fat or thin, for the
 
858
    # stat.
 
859
    $thinPPCForStat = $sourcePPC;
 
860
    $thinX86ForStat = $sourceX86;
 
861
  }
 
862
 
 
863
  if (!defined($sizePPC = $thinPPCForStat->statSize())) {
 
864
    unlink(@tempThinFiles);
 
865
    return complain(1, 'stat ppc: '.$!.' for:',
 
866
                    $thinPPCForStat->path());
 
867
  }
 
868
  if (!defined($sizeX86 = $thinX86ForStat->statSize())) {
 
869
    unlink(@tempThinFiles);
 
870
    return complain(1, 'stat x86: '.$!.' for:',
 
871
                    $thinX86ForStat->path());
 
872
  }
 
873
 
 
874
  $sizePPC = $sizePPC % 4096;
 
875
  $sizeX86 = $sizeX86 % 4096;
 
876
 
 
877
  my (@thinFiles);
 
878
 
 
879
  if ($sizePPC == 0) {
 
880
    # PPC image ends on an alignment boundary, there will be no padding before
 
881
    # starting the x86 image.
 
882
    @thinFiles = ($thinPPC->path(), $thinX86->path());
 
883
  }
 
884
  elsif ($sizeX86 == 0 || $sizeX86 > $sizePPC) {
 
885
    # x86 image ends on an alignment boundary, there will be no padding before
 
886
    # starting the PPC image, or the x86 image exceeds its alignment boundary
 
887
    # by more than the PPC image, so there will be less padding if the x86
 
888
    # comes first.
 
889
    @thinFiles = ($thinX86->path(), $thinPPC->path());
 
890
  }
 
891
  else {
 
892
    # PPC image exceeds its alignment boundary by more than the x86 image, so
 
893
    # there will be less padding if the PPC comes first.
 
894
    @thinFiles = ($thinPPC->path(), $thinX86->path());
 
895
  }
 
896
 
 
897
  my ($isExecutable);
 
898
  $isExecutable = $sourcePPC->lIsExecutable() ||
 
899
                  $sourceX86->lIsExecutable();
 
900
 
 
901
  if (!$gDryRun) {
 
902
    # Ensure that the file does not yet exist.
 
903
 
 
904
    # Set the execute bits (as allowed by the umask) on the new file if any
 
905
    # execute bit is set on either old file.  Yes, it is possible to have
 
906
    # proper Mach-O files without x-bits: think object files (.o) and static
 
907
    # archives (.a).
 
908
    if (!createUniqueFile($targetPath, $isExecutable ? 0777 : 0666)) {
 
909
      # createUniqueFile printed an error.
 
910
      unlink(@tempThinFiles);
 
911
      return 0;
 
912
    }
 
913
  }
 
914
 
 
915
  # Create the fat file.
 
916
  if (command($gConfig{'cmd_lipo'}, '-create', @thinFiles,
 
917
              '-output', $targetPath) != 0) {
 
918
    unlink(@tempThinFiles, $targetPath);
 
919
    return complain(1, 'lipo create fat failed for:',
 
920
                    @thinFiles,
 
921
                    $targetPath);
 
922
  }
 
923
 
 
924
  unlink(@tempThinFiles);
 
925
 
 
926
  if (!$gDryRun) {
 
927
    # lipo seems to think that it's free to set its own file modes that
 
928
    # ignore the umask, which is bogus when the rest of this script
 
929
    # respects the umask.
 
930
    if (!chmod(($isExecutable ? 0777 : 0666) & ~umask(), $targetPath)) {
 
931
      complain(1, 'makeUniversalFile: chmod: '.$!.' for',
 
932
               $targetPath);
 
933
      unlink($targetPath);
 
934
      return 0;
 
935
    }
 
936
  }
 
937
 
 
938
  return 1;
 
939
}
 
940
 
 
941
# makeUniversalInternal($isToplevel, $filePPC, $fileX86, $fileTargetPath)
 
942
#
 
943
# Given FileAttrCache objects $filePPC and $fileX86, compares filetypes
 
944
# and performs the appropriate action to produce a universal file at
 
945
# path string $fileTargetPath.  $isToplevel should be true if this is
 
946
# the recursive base and false otherwise; this controls cleanup behavior
 
947
# (cleanup is only performed at the base, because cleanup itself is
 
948
# recursive).
 
949
#
 
950
# This handles regular files by determining whether they are Mach-O files
 
951
# and calling makeUniversalFile if so and copyIfIdentical otherwise.  Symbolic
 
952
# links are handled directly in this function by ensuring that the source link
 
953
# targets are identical and creating a new link with the same target
 
954
# at $fileTargetPath.  Directories are handled by calling
 
955
# makeUniversalDirectory.
 
956
#
 
957
# One of $filePPC and $fileX86 is permitted to be undef.  In that case,
 
958
# the defined source file is copied directly to the target if a regular
 
959
# file, and symlinked appropriately if a symbolic link.  This facilitates
 
960
# use of $gOnlyOne = 'copy', although no $gOnlyOne checks are made in this
 
961
# function, they are all handled in makeUniversalDirectory.
 
962
#
 
963
# Returns true on success.  Returns false on failure, including failures
 
964
# in other functions called.
 
965
sub makeUniversalInternal($$$$) {
 
966
  my ($filePPC, $fileTargetPath, $fileX86, $isToplevel);
 
967
  ($isToplevel, $filePPC, $fileX86, $fileTargetPath) = @_;
 
968
 
 
969
  my ($typePPC, $typeX86);
 
970
  if (defined($filePPC) && !defined($typePPC = $filePPC->lstatType())) {
 
971
    return complain(1, 'makeUniversal: lstat ppc: '.$!.' for:',
 
972
                    $filePPC->path());
 
973
  }
 
974
  if (defined($fileX86) && !defined($typeX86 = $fileX86->lstatType())) {
 
975
    return complain(1, 'makeUniversal: lstat x86: '.$!.' for:',
 
976
                    $fileX86->path());
 
977
  }
 
978
 
 
979
  if (defined($filePPC) && defined($fileX86) && $typePPC != $typeX86) {
 
980
    return complain(1, 'makeUniversal: incompatible types:',
 
981
                    $filePPC->path(),
 
982
                    $fileX86->path());
 
983
  }
 
984
 
 
985
  # $aSourceFile will contain a FileAttrCache object that will return
 
986
  # the correct type data.  It's used because it's possible for one of
 
987
  # the two source files to be undefined (indicating a straight copy).
 
988
  my ($aSourceFile);
 
989
  if (defined($filePPC)) { 
 
990
    $aSourceFile = $filePPC;
 
991
  }
 
992
  else {
 
993
    $aSourceFile = $fileX86;
 
994
  }
 
995
 
 
996
  if ($aSourceFile->lIsDir()) {
 
997
    if ($gVerbosity >= 3 || $gDryRun) {
 
998
      print('mkdir '.(argumentEscape($fileTargetPath))[0]."\n");
 
999
    }
 
1000
    if (!$gDryRun && !mkdir($fileTargetPath)) {
 
1001
      return complain(1, 'makeUniversal: mkdir: '.$!.' for:',
 
1002
                      $fileTargetPath);
 
1003
    }
 
1004
 
 
1005
    my ($rv);
 
1006
 
 
1007
    if (!($rv = makeUniversalDirectory($filePPC, $fileX86, $fileTargetPath))) {
 
1008
      # makeUniversalDirectory printed an error.
 
1009
      if ($isToplevel) {
 
1010
        command($gConfig{'cmd_rm'},'-rf','--',$fileTargetPath);
 
1011
      }
 
1012
    }
 
1013
    else {
 
1014
      # Touch the directory when leaving it.  If unify is being run on an
 
1015
      # .app bundle, the .app might show up without an icon because the
 
1016
      # system might have found the .app before it was completely built.
 
1017
      # Touching it dirties it in LaunchServices' mind.
 
1018
      if ($gVerbosity >= 3) {
 
1019
        print('touch '.(argumentEscape($fileTargetPath))[0]."\n");
 
1020
      }
 
1021
      utime(undef, undef, $fileTargetPath);
 
1022
    }
 
1023
 
 
1024
    return $rv;
 
1025
  }
 
1026
  elsif ($aSourceFile->lIsSymLink()) {
 
1027
    my ($linkPPC, $linkX86);
 
1028
    if (defined($filePPC) && !defined($linkPPC=readlink($filePPC->path()))) {
 
1029
      return complain(1, 'makeUniversal: readlink ppc: '.$!.' for:',
 
1030
                      $filePPC->path());
 
1031
    }
 
1032
    if (defined($fileX86) && !defined($linkX86=readlink($fileX86->path()))) {
 
1033
      return complain(1, 'makeUniversal: readlink x86: '.$!.' for:',
 
1034
                      $fileX86->path());
 
1035
    }
 
1036
    if (defined($filePPC) && defined($fileX86) && $linkPPC ne $linkX86) {
 
1037
      return complain(1, 'makeUniversal: symbolic links differ:',
 
1038
                      $filePPC->path(),
 
1039
                      $fileX86->path());
 
1040
    }
 
1041
 
 
1042
    # $aLink here serves the same purpose as $aSourceFile in the enclosing
 
1043
    # block: it refers to the target of the symbolic link, whether there
 
1044
    # is one valid source or two.
 
1045
    my ($aLink);
 
1046
    if (defined($linkPPC)) {
 
1047
      $aLink = $linkPPC;
 
1048
    }
 
1049
    else {
 
1050
      $aLink = $linkX86;
 
1051
    }
 
1052
 
 
1053
    if ($gVerbosity >= 3 || $gDryRun) {
 
1054
      print('ln -s '.
 
1055
            join(' ',argumentEscape($aLink, $fileTargetPath))."\n");
 
1056
    }
 
1057
    if (!$gDryRun && !symlink($aLink, $fileTargetPath)) {
 
1058
      return complain(1, 'makeUniversal: symlink: '.$!.' for:',
 
1059
                      $aLink,
 
1060
                      $fileTargetPath);
 
1061
    }
 
1062
 
 
1063
    return 1;
 
1064
  }
 
1065
  elsif($aSourceFile->lIsRegularFile()) {
 
1066
    my ($machPPC, $machX86);
 
1067
    if (!defined($filePPC) || !defined($fileX86)) {
 
1068
      # One of the source files isn't present.  The right thing to do is
 
1069
      # to just copy what does exist straight over, so skip Mach-O checks.
 
1070
      $machPPC = 0;
 
1071
      $machX86 = 0;
 
1072
    }
 
1073
    else {
 
1074
      if (!defined($machPPC=$filePPC->isMachO())) {
 
1075
        return complain(1, 'makeUniversal: isFileMachO ppc failed for:',
 
1076
                        $filePPC->path());
 
1077
      }
 
1078
      if (!defined($machX86=$fileX86->isMachO())) {
 
1079
        return complain(1, 'makeUniversal: isFileMachO x86 failed for:',
 
1080
                        $fileX86->path());
 
1081
      }
 
1082
    }
 
1083
 
 
1084
    if ($machPPC != $machX86) {
 
1085
      return complain(1, 'makeUniversal: variant Mach-O attributes:',
 
1086
                      $filePPC->path(),
 
1087
                  $fileX86->path());
 
1088
    }
 
1089
 
 
1090
    if ($machPPC) {
 
1091
      # makeUniversalFile will print an error if it fails.
 
1092
      return makeUniversalFile($filePPC, $fileX86, $fileTargetPath);
 
1093
    }
 
1094
 
 
1095
    if (grep { $filePPC->path() =~ m/$_/; } @gSortMatches) {
 
1096
      # Regular files, but should be compared with sorting first.
 
1097
      # copyIfIdenticalWhenSorted will print an error if it fails.
 
1098
      return copyIfIdenticalWhenSorted($filePPC, $fileX86, $fileTargetPath);
 
1099
    }
 
1100
 
 
1101
    # Regular file.  copyIfIdentical will print an error if it fails.
 
1102
    return copyIfIdentical($filePPC, $fileX86, $fileTargetPath);
 
1103
  }
 
1104
 
 
1105
  # Special file, don't know how to handle.
 
1106
  return complain(1, 'makeUniversal: cannot handle special file:',
 
1107
                  $filePPC->path(),
 
1108
                  $fileX86->path());
 
1109
}
 
1110
 
 
1111
# usage()
 
1112
#
 
1113
# Give the user a hand.
 
1114
sub usage() {
 
1115
  print STDERR (
 
1116
"usage: unify <ppc-path> <x86-path> <universal-path>\n".
 
1117
"            [--dry-run]           (print what would be done)\n".
 
1118
"            [--only-one <action>] (skip, copy, fail; default=copy)\n".
 
1119
"            [--verbosity <level>] (0, 1, 2, 3; default=2)\n");
 
1120
  return;
 
1121
}
 
1122
 
 
1123
# readZipCRCs($zipFile)
 
1124
#
 
1125
# $zipFile is the pathname to a zip file whose directory will be read.
 
1126
# A reference to a hash is returned, with the member pathnames from the
 
1127
# zip file as keys, and reasonably unique identifiers as values.  The
 
1128
# format of the values is not specified exactly, but does include the
 
1129
# member CRCs and sizes and differentiates between files and directories.
 
1130
# It specifically does not distinguish between modification times.  On
 
1131
# failure, prints a message and returns undef.
 
1132
sub readZipCRCs($) {
 
1133
  my ($zipFile);
 
1134
  ($zipFile) = @_;
 
1135
 
 
1136
  my ($ze, $zip);
 
1137
  $zip = Archive::Zip->new();
 
1138
 
 
1139
  if (($ze = $zip->read($zipFile)) != AZ_OK) {
 
1140
    complain(1, 'readZipCRCs: read error '.$ze.' for:',
 
1141
             $zipFile);
 
1142
    return undef;
 
1143
  }
 
1144
 
 
1145
  my ($member, %memberCRCs, @memberList);
 
1146
  %memberCRCs = ();
 
1147
  @memberList = $zip->members();
 
1148
 
 
1149
  foreach $member (@memberList) {
 
1150
    # Take a few of the attributes that identify the file and stuff them into
 
1151
    # the members hash.  Directories will show up with size 0 and crc32 0,
 
1152
    # so isDirectory() is used to distinguish them from empty files.
 
1153
    $memberCRCs{$member->fileName()} = join(',', $member->isDirectory() ? 1 : 0,
 
1154
                                                 $member->uncompressedSize(),
 
1155
                                                 $member->crc32String());
 
1156
  }
 
1157
 
 
1158
  return {%memberCRCs};
 
1159
}
 
1160
 
 
1161
{
 
1162
  # FileAttrCache allows various attributes about a file to be cached
 
1163
  # so that if they are needed again after first use, no system calls
 
1164
  # will be made and the program won't need to hit the disk.
 
1165
 
 
1166
  package FileAttrCache;
 
1167
 
 
1168
  use Fcntl(':DEFAULT', ':mode');
 
1169
 
 
1170
  # FileAttrCache->new($path)
 
1171
  #
 
1172
  # Creates a new FileAttrCache object for the file at path $path and
 
1173
  # returns it.  The cache is not primed at creation time, values are
 
1174
  # fetched lazily as they are needed.
 
1175
  sub new($$) {
 
1176
    my ($class, $path, $proto, $this);
 
1177
    ($proto, $path) = @_;
 
1178
    if (!($class = ref($proto))) {
 
1179
      $class = $proto;
 
1180
    }
 
1181
    $this = {
 
1182
      'path'        => $path,
 
1183
      'lstat'       => undef,
 
1184
      'lstatErrno'  => 0,
 
1185
      'lstatInit'   => 0,
 
1186
      'magic'       => undef,
 
1187
      'magic2'       => undef,
 
1188
      'magicErrno'  => 0,
 
1189
      'magicErrMsg' => undef,
 
1190
      'magicInit'   => 0,
 
1191
      'stat'        => undef,
 
1192
      'statErrno'   => 0,
 
1193
      'statInit'    => 0,
 
1194
    };
 
1195
    bless($this, $class);
 
1196
    return($this);
 
1197
  }
 
1198
 
 
1199
  # $FileAttrCache->isFat()
 
1200
  #
 
1201
  # Returns true if the file is a fat Mach-O file, false if it's not, and
 
1202
  # undef if an error occurs.  See /usr/include/mach-o/fat.h.
 
1203
  sub isFat($) {
 
1204
    my ($magic, $magic2, $this);
 
1205
    ($this) = @_;
 
1206
 
 
1207
    # magic() caches, there's no separate cache because isFat() doesn't hit
 
1208
    # the disk other than by calling magic().
 
1209
 
 
1210
    if (!defined($magic = $this->magic())) {
 
1211
      return undef;
 
1212
    }
 
1213
    $magic2 = $this->magic2();
 
1214
 
 
1215
    # We have to sanity check the second four bytes, because Java class
 
1216
    # files use the same magic number as Mach-O fat binaries.
 
1217
    # This logic is adapted from file(1), which says that Mach-O uses
 
1218
    # these bytes to count the number of architectures within, while
 
1219
    # Java uses it for a version number. Conveniently, there are only
 
1220
    # 18 labelled Mach-O architectures, and Java's first released
 
1221
    # class format used the version 43.0.
 
1222
    if ($magic == 0xcafebabe && $magic2 < 20) {
 
1223
      return 1;
 
1224
    }
 
1225
 
 
1226
    return 0;
 
1227
  }
 
1228
 
 
1229
  # $FileAttrCache->isMachO()
 
1230
  #
 
1231
  # Returns true if the file is a Mach-O image (including a fat file), false
 
1232
  # if it's not, and undef if an error occurs.  See
 
1233
  # /usr/include/mach-o/loader.h and /usr/include/mach-o/fat.h.
 
1234
  sub isMachO($) {
 
1235
    my ($magic, $this);
 
1236
    ($this) = @_;
 
1237
 
 
1238
    # magic() caches, there's no separate cache because isMachO() doesn't hit
 
1239
    # the disk other than by calling magic().
 
1240
 
 
1241
    if (!defined($magic = $this->magic())) {
 
1242
      return undef;
 
1243
    }
 
1244
 
 
1245
    # Accept Mach-O fat files or Mach-O thin files of either endianness.
 
1246
    if ($magic == 0xfeedface ||
 
1247
        $magic == 0xcefaedfe ||
 
1248
        $this->isFat()) {
 
1249
      return 1;
 
1250
    }
 
1251
 
 
1252
    return 0;
 
1253
  }
 
1254
 
 
1255
  # $FileAttrCache->isZip()
 
1256
  #
 
1257
  # Returns true if the file is a zip file, false if it's not, and undef if
 
1258
  # an error occurs.  See http://www.pkware.com/business_and_developers/developer/popups/appnote.txt .
 
1259
  sub isZip($) {
 
1260
    my ($magic, $this);
 
1261
    ($this) = @_;
 
1262
 
 
1263
    # magic() caches, there's no separate cache because isFat() doesn't hit
 
1264
    # the disk other than by calling magic().
 
1265
 
 
1266
    if (!defined($magic = $this->magic())) {
 
1267
      return undef;
 
1268
    }
 
1269
 
 
1270
    if ($magic == 0x504b0304) {
 
1271
      return 1;
 
1272
    }
 
1273
 
 
1274
    return 0;
 
1275
  }
 
1276
 
 
1277
  # $FileAttrCache->lIsExecutable()
 
1278
  #
 
1279
  # Wraps $FileAttrCache->lstat(), returning true if the file is has any,
 
1280
  # execute bit set, false if none are set, or undef if an error occurs.
 
1281
  # On error, $! is set to lstat's errno.
 
1282
  sub lIsExecutable($) {
 
1283
    my ($mode, $this);
 
1284
    ($this) = @_;
 
1285
 
 
1286
    if (!defined($mode = $this->lstatMode())) {
 
1287
      return undef;
 
1288
    }
 
1289
 
 
1290
    return $mode & (S_IXUSR | S_IXGRP | S_IXOTH);
 
1291
  }
 
1292
 
 
1293
  # $FileAttrCache->lIsDir()
 
1294
  #
 
1295
  # Wraps $FileAttrCache->lstat(), returning true if the file is a directory,
 
1296
  # false if it isn't, or undef if an error occurs.  Because lstat is used,
 
1297
  # this will return false even if the file is a symlink pointing to a
 
1298
  # directory.  On error, $! is set to lstat's errno.
 
1299
  sub lIsDir($) {
 
1300
    my ($type, $this);
 
1301
    ($this) = @_;
 
1302
 
 
1303
    if (!defined($type = $this->lstatType())) {
 
1304
      return undef;
 
1305
    }
 
1306
 
 
1307
    return S_ISDIR($type);
 
1308
  }
 
1309
 
 
1310
  # $FileAttrCache->lIsRegularFile()
 
1311
  #
 
1312
  # Wraps $FileAttrCache->lstat(), returning true if the file is a regular,
 
1313
  # file, false if it isn't, or undef if an error occurs.  Because lstat is
 
1314
  # used, this will return false even if the file is a symlink pointing to a
 
1315
  # regular file.  On error, $! is set to lstat's errno.
 
1316
  sub lIsRegularFile($) {
 
1317
    my ($type, $this);
 
1318
    ($this) = @_;
 
1319
 
 
1320
    if (!defined($type = $this->lstatType())) {
 
1321
      return undef;
 
1322
    }
 
1323
 
 
1324
    return S_ISREG($type);
 
1325
  }
 
1326
 
 
1327
  # $FileAttrCache->lIsSymLink()
 
1328
  #
 
1329
  # Wraps $FileAttrCache->lstat(), returning true if the file is a symbolic,
 
1330
  # link, false if it isn't, or undef if an error occurs.  On error, $! is
 
1331
  # set to lstat's errno.
 
1332
  sub lIsSymLink($) {
 
1333
    my ($type, $this);
 
1334
    ($this) = @_;
 
1335
   
 
1336
    if (!defined($type = $this->lstatType())) {
 
1337
      return undef;
 
1338
    }
 
1339
 
 
1340
    return S_ISLNK($type);
 
1341
  }
 
1342
 
 
1343
  # $FileAttrCache->lstat()
 
1344
  #
 
1345
  # Wraps the lstat system call, providing a cache to speed up multiple
 
1346
  # lstat calls for the same file.  See lstat(2) and lstat in perlfunc(1).
 
1347
  sub lstat($) {
 
1348
    my (@stat, $this);
 
1349
    ($this) = @_;
 
1350
 
 
1351
    # Use the cached lstat result.
 
1352
    if ($$this{'lstatInit'}) {
 
1353
      if (defined($$this{'lstatErrno'})) {
 
1354
        $! = $$this{'lstatErrno'};
 
1355
      }
 
1356
      return @{$$this{'lstat'}};
 
1357
    }
 
1358
    $$this{'lstatInit'} = 1;
 
1359
 
 
1360
    if (!(@stat = CORE::lstat($$this{'path'}))) {
 
1361
      $$this{'lstatErrno'} = $!;
 
1362
    }
 
1363
 
 
1364
    $$this{'lstat'} = [@stat];
 
1365
    return @stat;
 
1366
  }
 
1367
 
 
1368
  # $FileAttrCache->lstatMode()
 
1369
  #
 
1370
  # Wraps $FileAttrCache->lstat(), returning the mode bits from the st_mode
 
1371
  # field, or undef if an error occurs.  On error, $! is set to lstat's
 
1372
  # errno.
 
1373
  sub lstatMode($) {
 
1374
    my (@stat, $this);
 
1375
    ($this) = @_;
 
1376
 
 
1377
    if (!(@stat = $this->lstat())) {
 
1378
      return undef;
 
1379
    }
 
1380
 
 
1381
    return S_IMODE($stat[2]);
 
1382
  }
 
1383
 
 
1384
  # $FileAttrCache->lstatType()
 
1385
  #
 
1386
  # Wraps $FileAttrCache->lstat(), returning the type bits from the st_mode
 
1387
  # field, or undef if an error occurs.  On error, $! is set to lstat's
 
1388
  # errno.
 
1389
  sub lstatType($) {
 
1390
    my (@stat, $this);
 
1391
    ($this) = @_;
 
1392
 
 
1393
    if (!(@stat = $this->lstat())) {
 
1394
      return undef;
 
1395
    }
 
1396
 
 
1397
    return S_IFMT($stat[2]);
 
1398
  }
 
1399
 
 
1400
  # $FileAttrCache->magic()
 
1401
  #
 
1402
  # Returns the "magic number" for the file by reading its first four bytes
 
1403
  # as a big-endian unsigned 32-bit integer and returning the result.  If an
 
1404
  # error occurs, returns undef and prints diagnostic messages to stderr.  If
 
1405
  # the file is shorter than 32 bits, returns -1.  A cache is provided to
 
1406
  # speed multiple magic calls for the same file.
 
1407
  sub magic($) {
 
1408
    my ($this);
 
1409
    ($this) = @_;
 
1410
 
 
1411
    # Use the cached magic result.
 
1412
    if ($$this{'magicInit'}) {
 
1413
      if (defined($$this{'magicErrno'})) {
 
1414
        if (defined($$this{'magicErrMsg'})) {
 
1415
          complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
 
1416
                   $$this{'path'});
 
1417
        }
 
1418
        $! = $$this{'magicErrno'};
 
1419
      }
 
1420
      return $$this{'magic'};
 
1421
    }
 
1422
 
 
1423
    $$this{'magicInit'} = 1;
 
1424
 
 
1425
    my ($fh);
 
1426
    if (!sysopen($fh, $$this{'path'}, O_RDONLY)) {
 
1427
      $$this{'magicErrno'} = $!;
 
1428
      $$this{'magicErrMsg'} = 'open "'.$$this{'path'}.'": '.$!;
 
1429
      complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
 
1430
               $$this{'path'});
 
1431
      return undef;
 
1432
    }
 
1433
 
 
1434
    $! = 0;
 
1435
    my ($bytes, $magic, $bytes2, $magic2);
 
1436
    if (!defined($bytes = sysread($fh, $magic, 4))) {
 
1437
      $$this{'magicErrno'} = $!;
 
1438
      $$this{'magicErrMsg'} = 'read "'.$$this{'path'}.'": '.$!;
 
1439
      complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
 
1440
               $$this{'path'});
 
1441
      close($fh);
 
1442
      return undef;
 
1443
    }
 
1444
    else {
 
1445
      $bytes2 = sysread($fh, $magic2, 4);
 
1446
    }
 
1447
 
 
1448
    close($fh);
 
1449
 
 
1450
    if ($bytes != 4) {
 
1451
      # The file is too short, didn't read a magic number.  This isn't really
 
1452
      # an error.  Return an unlikely value.
 
1453
      $$this{'magic'} = -1;
 
1454
      $$this{'magic2'} = -1;
 
1455
      return -1;
 
1456
    }
 
1457
    if ($bytes2 != 4) {
 
1458
      # File is too short to read a second 4 bytes.
 
1459
      $magic2 = -1;
 
1460
    }
 
1461
 
 
1462
    $$this{'magic'} = unpack('N', $magic);
 
1463
    $$this{'magic2'} = unpack('N', $magic2);
 
1464
    return $$this{'magic'};
 
1465
  }
 
1466
 
 
1467
  # $FileAttrCache->magic2()
 
1468
  #
 
1469
  # Returns the second four bytes of the file as a 32-bit little endian number.
 
1470
  # See magic(), above for more info.
 
1471
  sub magic2($) {
 
1472
    my ($this);
 
1473
    ($this) = @_;
 
1474
 
 
1475
    # we do the actual work (and cache it) in magic().
 
1476
    if (!$$this{'magicInit'}) {
 
1477
      my $magic = $$this->magic();
 
1478
    }
 
1479
 
 
1480
    return $$this{'magic2'};
 
1481
  }
 
1482
 
 
1483
  # $FileAttrCache->path()
 
1484
  #
 
1485
  # Returns the file's pathname.
 
1486
  sub path($) {
 
1487
    my ($this);
 
1488
    ($this) = @_;
 
1489
    return $$this{'path'};
 
1490
  }
 
1491
 
 
1492
  # $FileAttrCache->stat()
 
1493
  #
 
1494
  # Wraps the stat system call, providing a cache to speed up multiple
 
1495
  # stat calls for the same file.  If lstat() has already been called and
 
1496
  # the file is not a symbolic link, the cached lstat() result will be used.
 
1497
  # See stat(2) and lstat in perlfunc(1).
 
1498
  sub stat($) {
 
1499
    my (@stat, $this);
 
1500
    ($this) = @_;
 
1501
 
 
1502
    # Use the cached stat result.
 
1503
    if ($$this{'statInit'}) {
 
1504
      if (defined($$this{'statErrno'})) {
 
1505
        $! = $$this{'statErrno'};
 
1506
      }
 
1507
      return @{$$this{'stat'}};
 
1508
    }
 
1509
 
 
1510
    $$this{'statInit'} = 1;
 
1511
 
 
1512
    # If lstat has already been called, and the file isn't a symbolic link,
 
1513
    # use the cached lstat result.
 
1514
    if ($$this{'lstatInit'} && !$$this{'lstatErrno'} &&
 
1515
        !S_ISLNK(${$$this{'lstat'}}[2])) {
 
1516
      $$this{'stat'} = $$this{'lstat'};
 
1517
      return @{$$this{'stat'}};
 
1518
    }
 
1519
 
 
1520
    if (!(@stat = CORE::stat($$this{'path'}))) {
 
1521
      $$this{'statErrno'} = $!;
 
1522
    }
 
1523
 
 
1524
    $$this{'stat'} = [@stat];
 
1525
    return @stat;
 
1526
  }
 
1527
 
 
1528
  # $FileAttrCache->statSize()
 
1529
  #
 
1530
  # Wraps $FileAttrCache->stat(), returning the st_size field, or undef
 
1531
  # undef if an error occurs.  On error, $! is set to stat's errno.
 
1532
  sub statSize($) {
 
1533
    my (@stat, $this);
 
1534
    ($this) = @_;
 
1535
 
 
1536
    if (!(@stat = $this->lstat())) {
 
1537
      return undef;
 
1538
    }
 
1539
 
 
1540
    return $stat[7];
 
1541
  }
 
1542
}