#!/usr/bin/perl -w

# This script applies 'lipo' to directories.  For each file present in
# any source directory, it will create a file in the destination directory.
# If all the copies of the file in the source directories are the same,
# the file is copied directly to the destination.  If there are different
# files in different directories, but the files are executable, 
# lipo is run to merge the files together.  Otherwise, an error is
# produced.

# Device files and pipes are not supported, but symlinks are; the script
# will check that all the symlinks point to the same place.
# If a file is present in only one of the source directories, it is
# trivially the same as itself and so it is just copied to the destination.
# If there is only one source directory, the script is just an expensive
# version of 'cp -rp'.
# The destination directory is not emptied by this script.

use File::Find ();
use File::Copy ();
use File::Compare ();
use POSIX ();

if ($#ARGV < 1) {
    print "usage: $0 <source-directories ...> <dest-directory>\n";
    exit 1;
}

# The source directories.
my @sources = @ARGV[0..$#ARGV-1];
# The destination directory.
my $dest = $ARGV[-1];

# Count of errors.
my $errors = 0;

# A hash of the files that need to be lipoed.  Only the keys of the 
# hash are significant.
my %needs_lipo = ();

# Print an error message.
sub error {
    printf STDERR "%s\n",$_[0];
    $errors++;
}

# First, scan the directories; copy any files to the destination that are
# identical in the source directory, print error messages, and make
# a list of the files that need lipo-ing.
foreach my $s (@sources)
{
    sub eachfile {
	my $destname = $dest . '/' . $File::Find::name;
	if (-d) {
	    if (-l) {
		    my $link1 = readlink;
				symlink $link1,$destname or error ("$destname: $!");
	    } else {
	      (mkdir $destname or error ("$destname: $!")) unless (-d $destname);
	    }
	} elsif (-l) {
	    my $link1 = readlink;
	    defined $link1 or error ("$s/$_: $!");
	    if (-l $destname) {
		my $link2 = readlink $destname;
		defined $link2 or error ("$destname: $!");
		$link1 eq $link2 or error ("$destname: different symlinks");
	    } elsif (-e $destname) {
		error ("$destname: symlink vs. real file");
	    } else {
		symlink $link1,$destname or error ("$destname: $!");
	    }
	} elsif (! -f) {
	    error ("$destname: no idea how to handle special file");
	} elsif (! -f $destname) {
	    system ("cp", "-p", $_, $destname) == 0 or $errors++;
	} elsif (File::Compare::compare ($_, $destname) != 0) {
	    $needs_lipo{$File::Find::name} = 1;
	}
    }

    chdir $s;
    File::Find::find (\&eachfile, '.');
}

# Run lipo.
foreach my $l (keys %needs_lipo) {
    # A list of the files to run lipo on.
    my @f = ();
    # Work out which source directories actually contain this file and
    # fill @f.
    foreach my $s (@sources) {
	my $sf = $s . '/' . $l;
	push @f,($sf) if (-f $sf);
    }
    die "inconsistent directories" if ($#f == -1);
    # There might really only be one copy if the file was present
    # in the tree before the script was started.
    if ($#f == 0) {
	system ("cp", "-p", $f[0], $dest . '/' . $l) == 0
	    or $errors++;
    } else {
	system ("lipo", "-create", "-output", $dest . '/' . $l, @f) == 0
	    or $errors++;
    }
}

exit ($errors ? 1 : 0);
