#!/usr/bin/env perl eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' & eval 'exec perl -w -S $0 $argv:q' if 0; # ****************************************************************** # Author: Chad Elliott # Create Date: 3/09/2004 # ****************************************************************** # ****************************************************************** # Pragma Section # ****************************************************************** use strict; use Cwd; use FileHandle; use File::Copy; use File::Basename; use File::Find; # ****************************************************************** # Data Section # ****************************************************************** my $insext = 'ins'; my $version = '2.0'; my %defaults = ('header_files' => 1, 'idl_files' => 1, 'inline_files' => 1, 'pidl_files' => 1, 'template_files' => 1, 'mpb_files' => 1, ); my %special = ('exe_output' => 1, 'lib_output' => 1, ); my %extended; my %actual; my %base; my %override; my $keepgoing = 0; my $binarydir = ''; eval 'symlink("", "");'; my $hasSymlink = ($@ eq ''); # ****************************************************************** # Subroutine Section # ****************************************************************** sub rm_updirs { my $path = shift; my @parts = split(/[\/\\]/, $path); ## Split the path into parts and check for '..'. If we find one ## and the previous entry wasn't one, then we can remove them both. for(my $i = 0; $i <= $#parts; $i++) { if ($i > 0 && $parts[$i] eq '..' && $parts[$i - 1] ne '..') { splice(@parts, $i - 1, 2); $i -= 2; } } return join('/', @parts); } sub get_dest { my($file, $insdir) = @_; return rm_updirs($insdir . '/' . (defined $actual{$file} ? "$actual{$file}/" . basename($file) : $file)); } sub copyFiles { my($files, $insdir, $symlink, $verbose) = @_; my $type = ($symlink ? 'link' : 'copy'); my $cwd = getcwd(); # If > 1 file in @$files is a symlink to the same target, create one of the # files (the shorter-named one) as a symlink in the destination tree. my %localLink; # keys: files to create as symlinks in the destination # values: symlink target (used in the main loop, below) my %linkTargets; # keys: link targets in the source tree # values: name of the symlink in the source tree foreach my $file (@$files) { if (-l $file) { my $dest = readlink($file); if (exists $linkTargets{$dest} && $file ne $linkTargets{$dest}) { if (length $file <= length $linkTargets{$dest}) { $localLink{$file} = $linkTargets{$dest}; } else { $localLink{$linkTargets{$dest}} = $file; $linkTargets{$dest} = $file; } } else { $linkTargets{$dest} = $file; } } } foreach my $file (@$files) { my $dest = get_dest($file, $insdir); my $fulldir = dirname($dest); if (! -d $fulldir) { my $tmp = ''; foreach my $part (split(/[\/\\]/, $fulldir)) { $tmp .= $part . '/'; mkdir($tmp, 0755); } } if (! -e $dest || (-M $file) < (-M $dest)) { if ($verbose) { print '', ($symlink ? 'Linking' : 'Copying'), " to $dest\n"; } my $status; if ($symlink) { unlink($dest); $status = symlink("$cwd/$file", $dest); } elsif (exists $localLink{$file}) { my $target = get_dest($localLink{$file}, $insdir); if ($fulldir eq dirname($target)) { $target = basename($target); } if ($verbose) { print "\tCreating local symlink to $target\n"; } unlink($dest); $status = symlink($target, $dest); } else { $status = copy($file, $dest); chmod(0755, $dest) if ($status && -x $file); } if (!$status) { print STDERR "ERROR: Unable to $type $file to $dest\n"; if (!$keepgoing) { return 0; } } } else { print "Skipping $file\n" if ($verbose); } } return 1; } sub determineSpecialName { my($tag, $dir, $info) = @_; my($insdir, $name) = split(/\s+/, $info); if (defined $name) { $insdir .= '/'; } else { $name = $insdir; $insdir = ''; } my $odir = ($insdir =~ /^\// ? $insdir : ($dir . $insdir)) . $binarydir; $odir = '.' if $odir eq ''; if ($tag eq 'exe_output') { my @exes; my $fh = new FileHandle(); if (opendir($fh, $odir)) { foreach my $file (grep(!/^\.\.?$/, readdir($fh))) { if ($file =~ /^$name$/ || $file =~ /^$name.*\.exe$/i) { push(@exes, "$dir$insdir$binarydir$file"); } } closedir($fh); } return @exes; } elsif ($tag eq 'lib_output') { my @libs; my $fh = new FileHandle(); if (opendir($fh, $odir)) { foreach my $file (grep(!/^\.\.?$/, readdir($fh))) { if ($file =~ /^lib$name(-[0-9]+(\.[0-9]+)*)?\.(a|so|sl|dylib)/ || $file =~ /^(lib)?$name.*\.(dll|lib)$/i) { push(@libs, "$dir$insdir$binarydir$file"); } } closedir($fh); } return @libs; } return "$dir$name"; } sub replaceVariables { my $line = shift; while($line =~ /(\$\(([^)]+)\))/) { my $whole = $1; my $name = $2; my $val = (defined $ENV{$name} ? $ENV{$name} : ''); $line =~ s/\$\([^)]+\)/$val/; } return $line; } sub loadInsFiles { my($files, $tags, $verbose) = @_; my $fh = new FileHandle(); my @copy; foreach my $file (@$files) { if (open($fh, $file)) { if ($verbose) { print "Loading $file\n"; } my $base = dirname($file); if ($base eq '.') { $base = ''; } else { $base =~ s/^\.[\/\\]+//; $base .= '/'; } my $current; while(<$fh>) { my $line = $_; $line =~ s/^\s+//; $line =~ s/\s+$//; if ($line ne '') { if ($line =~ /^(\w+):$/) { if (defined $$tags{$1}) { $current = $1; } else { $current = undef; } } elsif (defined $current) { $line = replaceVariables($line); my $perFileOverride = undef; my $start = $#copy + 1; if (defined $special{$current}) { push(@copy, determineSpecialName($current, $base, $line)); } else { my($src, $dst); if ($line =~ /^\"([^"]+)\" (.*)/ || $line =~ /^(\S+) (\S+)/) { ($src, $dst) = ($1, $2); } else { ($src, $dst) = ($line, ''); } if (defined $extended{$current}) { push(@copy, "$base$src"); $perFileOverride = $dst; } else { push(@copy, "$base$src"); } } if (-d $copy[-1]) { my $dir = pop @copy; my $replace = $perFileOverride ? ($perFileOverride . '/' . basename($dir)) : ''; find({no_chdir => 1, wanted => sub { if (!(-d || /\.svn\// || /~$/)) { push @copy, $_; my $rel = $_; $rel =~ s/^$dir/$replace/; $actual{$_} = (defined $override{$current} ? $override{$current} : $base{$current}) . '/' . dirname($rel); } } }, $dir); } elsif (defined $override{$current}) { for(my $i = $start; $i <= $#copy; ++$i) { $actual{$copy[$i]} = $override{$current}; } } elsif (defined $base{$current}) { for(my $i = $start; $i <= $#copy; ++$i) { $actual{$copy[$i]} = $base{$current} . '/' . ($perFileOverride ? $perFileOverride : dirname($copy[$i])); } } } } } close($fh); } else { print STDERR "Unable to open $file\n"; return (); } } return @copy; } sub getInsFiles { my $file = shift; my @files; if (-d $file) { my $fh = new FileHandle(); if (opendir($fh, $file)) { foreach my $f (grep(!/^\.\.?$/, readdir($fh))) { push(@files, getInsFiles("$file/$f")); } closedir($fh); } } elsif ($file =~ /\.$insext$/) { push(@files, $file); } return @files; } sub usageAndExit { my $msg = shift; print STDERR "$msg\n" if (defined $msg); my $base = basename($0); my $spc = ' ' x (length($base) + 8); print STDERR "$base v$version\n", "Usage: $base [-a tag1[,tagN]] [-b tag=dir] [-d dir] ", ($hasSymlink ? '[-l] ' : ''), "[-o tag=dir]\n", $spc, "[-s tag1[,tagN]] [-x tag1[,tagN]] [-v] [-k] [-i]\n", $spc, "[install directory] [$insext files or directories]\n\n", "Install files matching the tag specifications found ", "in $insext files.\n\n", "-a Adds to the default set of tags that get copied.\n", "-b Install tag into dir underneath the install directory.\n", "-d Libs/executables are copied from this sub-directory.\n", "-i Read standard input in place of the $insext file.\n", "-k Keep going if a file to be copied is missing.\n", ($hasSymlink ? "-l Use symbolic links instead of copying.\n" : ''), "-o Install tag into dir.\n", "-s Sets the tags that get copied.\n", "-v Enables verbose mode.\n", "-x Enable extended behavior for the given tags:\n", " These tags will use the 'gendir' setting from the\n", " project as a relative target directory.\n", "\n", "The default set of tags are:\n"; my $first = 1; foreach my $key (sort keys %defaults) { print STDERR '', ($first ? '' : ', '), $key; $first = 0; } print STDERR "\n"; exit(0); } # ****************************************************************** # Main Section # ****************************************************************** my $verbose; my $first = 1; my $insdir; my $symlink; my @insfiles; my %tags = %defaults; for(my $i = 0; $i <= $#ARGV; ++$i) { my $arg = $ARGV[$i]; if ($arg =~ /^-/) { if ($arg eq '-a') { ++$i; if (defined $ARGV[$i]) { foreach my $tag (split(',', $ARGV[$i])) { $tags{$tag} = 1; } } else { usageAndExit('-a requires a parameter.'); } } elsif ($arg eq '-b') { ++$i; if (defined $ARGV[$i]) { if ($ARGV[$i] =~ /([^=]+)=(.*)/) { $base{$1} = $2; } else { usageAndExit("Invalid parameter to -b: $ARGV[$i]"); } } else { usageAndExit('-b requires a parameter.'); } } elsif ($arg eq '-d') { ++$i; if (defined $ARGV[$i]) { $binarydir = $ARGV[$i]; $binarydir =~ s/\\/\//g; $binarydir .= '/' unless $binarydir =~ /\/$/; } else { usageAndExit('-d requires a parameter.'); } } elsif ($arg eq '-i') { push(@insfiles, '-'); } elsif ($arg eq '-k') { $keepgoing = 1; } elsif ($arg eq '-l') { $symlink = $hasSymlink; } elsif ($arg eq '-o') { ++$i; if (defined $ARGV[$i]) { if ($ARGV[$i] =~ /([^=]+)=(.*)/) { $override{$1} = $2; } else { usageAndExit("Invalid parameter to -o: $ARGV[$i]"); } } else { usageAndExit('-o requires a parameter.'); } } elsif ($arg eq '-s') { ++$i; if (defined $ARGV[$i]) { %tags = (); foreach my $tag (split(',', $ARGV[$i])) { $tags{$tag} = 1; } } else { usageAndExit('-s requires a parameter.'); } } elsif ($arg eq '-v') { $verbose = 1; } elsif ($arg eq '-x') { ++$i; if (defined $ARGV[$i]) { foreach my $tag (split(',', $ARGV[$i])) { $extended{$tag} = 1; } } else { usageAndExit('-x requires a parameter.'); } } else { usageAndExit('Unknown option: ' . $arg); } } elsif (!defined $insdir) { $arg =~ s/\\/\//g; $insdir = $arg; } else { if ($first) { $first = 0; if ($verbose) { print "Collecting $insext files...\n"; } } $arg =~ s/\\/\//g; push(@insfiles, getInsFiles($arg)); } } if (!defined $insdir) { usageAndExit(); } elsif (!defined $insfiles[0]) { print "No $insext files were found.\n"; exit(1); } my $status = 1; my @files = loadInsFiles(\@insfiles, \%tags, $verbose); if (defined $files[0]) { $status = (copyFiles(\@files, $insdir, $symlink, $verbose) ? 0 : 1); } exit($status);