openbsd-ports/infrastructure/install/make-plist
2004-07-22 22:24:51 +00:00

499 lines
12 KiB
Perl
Executable File

#!/usr/bin/perl -w
# $OpenBSD: make-plist,v 1.39 2004/07/22 22:24:51 espie Exp $
# Copyright (c) 1999 Marc Espie
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
use strict;
use IO::File;
use File::Find;
use File::Spec;
use File::Temp qw/ tempdir /;
my $manual = 0;
my %out;
my @has_shared;
my ($plist, $pshared);
sub is_library
{
my $filename = shift;
return 0 unless $filename =~ m/\.so(?:\.\d+\.\d+)?$/;
my $check=`/usr/bin/file $filename`;
chomp $check;
if ($check =~m/\: ELF (32|64)-bit (MSB|LSB) shared object\,/ ||
$check =~m/OpenBSD\/.* demand paged shared library/) {
return 1;
} else {
return 0;
}
}
sub is_info
{
my $filename = shift;
return 0 unless $filename =~ m/\.info$/;
open my $fh, '<', $filename or return 0;
my $tag = <$fh>;
chomp $tag;
close $fh;
if ($tag =~ /^This is .*, produced by makeinfo version .* from/) {
return 1;
} else {
return 0;
}
}
sub is_dir
{
my $filename = shift;
return 0 unless $filename =~ m/\/dir$/;
return 1;
}
sub is_subinfo
{
my $filename = shift;
return 0 unless $filename =~ m/^(.*\.info)\-\d+$/;
return is_info($1);
}
{
package Annotation;
sub new {
my $class = shift;
bless {}, $class;
}
sub add {
my $object = shift;
my $key = shift;
unless (defined $object->{$key}) {
$object->{$key} = [];
}
push(@{$object->{$key}}, @_);
}
}
my $annotated = new Annotation;
my $annotated_dir = new Annotation;
sub annotate
{
my ($oldfh, $name, $newfh, $is_shared) = @_;
my $comment_printed = 0;
my ($mode, $owner, $group, $nocheck) = ('', '', '', '');
while(<$oldfh>) {
chomp;
if (m/^\@mode\s*/) {
$mode = $';
} elsif (m/^\@owner\s*/) {
$owner = $';
} elsif (m/^\@group\s*/) {
$group = $';
} elsif (m/^\@comment\s+no checksum$/) {
$nocheck = 1;
} elsif (m/^\@option no-default-conflict/||m/^\@pkgcfl/) {
printf $newfh "$_\n";
} elsif (m/^\@exec\s+/ || m/^\@unexec\s+/) {
$_=$';
# we don't warn for stuff we probably added...
next if m/^mkdir -p/||m/^install-info /;
$manual = 1;
} elsif (m/^\@comment\s+\$OpenBSD\:.*\$$/) {
print $newfh "$_\n";
$comment_printed = 1;
next;
} elsif (m/^\@comment\s+/) {
$_ = $';
if (m/^\@dirrm\s+(\S.*)/ || m/^\@dir\s+(\S.*)/ || m/^([^@].*)\/$/) {
$_ = $1;
$annotated_dir->add($_, [ $name, $newfh, $is_shared, 'comment']);
next;
} else {
$annotated->add($_, [ $name, $newfh, $is_shared, 'comment' ] );
}
next;
} elsif (m/^\@dirrm\s+(\S.*)/ || m/^\@dir\s+(\S.*)/ || m/^([^@].*)\/$/) {
$_=$1;
$annotated_dir->add($_, [ $name, $newfh, $is_shared ]);
next;
} elsif (m/^\@extra\b/ || m/^\@extraunexec\b/) {
$manual = 1;
next;
} elsif (m/^\@info\s+/) {
$_=$';
} elsif (m/^\@/) {
$manual = 1;
next;
} elsif (m/^\!?\%\%(.*)\%\%/) {
my $frag = $1;
if ($frag ne "SHARED") {
print $newfh "$_\n";
$manual = 1;
}
} elsif (m/\$\{.*\}/) {
$manual = 1;
}
if ("$mode$owner$group$nocheck" ne '') {
$annotated->add($_, [ $name, $newfh, $is_shared, $mode, $owner, $group, $nocheck ]);
$nocheck = '';
} else {
$annotated->add($_, [ $name, $newfh, $is_shared ]);
}
}
print $newfh "\@comment \$OpenBSD\$\n" unless $comment_printed;
}
# read an mtree file, and produce the corresponding directory hierarchy
sub parse_mtree
{
# start under current DESTDIR, usually
my $current = shift;
local(*FILE);
my %mtree;
open FILE, $ENV{MTREE_FILE};
while(<FILE>) {
chomp;
s/^\s*//;
next if /^\#/ || /^\//;
s/\s.*$//;
next if /^$/;
if ($_ eq '..') {
$current =~ s|/[^/]*$||;
next;
} else {
$current.="/$_";
}
$_ = $current;
while (s|/\./|/|) {}
$mtree{$_} = 1;
}
close FILE;
return \%mtree;
}
# prefix to remove from everything
my $base = $ENV{PREFIX};
my @backsubst;
sub strip
{
local($_) = shift;
s|^\Q$base\E/||;
for my $l (@backsubst) {
my $v = $l->[1];
my $r = $l->[0];
s/\Q$v\E/$r/g;
}
# If the resulting name is arch-dependent, we warn.
# We don't fix it automatically, as this may need special handling.
if (m/i386|m68k|sparc/) {
print STDERR "make-plist: generated plist contains arch-dependent\n";
print STDERR "\t$_\n";
}
return $_;
}
sub augment_mtree
{
my ($mtree, $dst, $pkg) = @_;
my $basepath;
local(*FILE);
system("tar zxqf $pkg +CONTENTS");
open(FILE, '<', '+CONTENTS') or
die "Error in dependent package $pkg\n";
while (<FILE>) {
chomp;
if (m/^\@dirrm\b/ || m/^\@dir\b/) {
die "Badly formed package: $pkg\n";
}
if (m/^\@cwd\s+/) {
$basepath = $';
last;
}
}
unless (defined $basepath and
File::Spec->file_name_is_absolute($basepath)) {
die "Badly formed package: $pkg\n";
}
while (<FILE>) {
chomp;
if (m/^\@dirrm\s+(\S.*)/ || m/^\@dir\s+(\S.*)/ || m/^([^@].*)\/$/) {
my $filename= File::Spec->catfile($dst, $basepath, $1);
$mtree->{$filename} = $pkg;
}
}
close(FILE);
unlink('+CONTENTS');
}
sub handle_file
{
my ($newdir, $item, $out, $is_lib) = @_;
my $fname = strip($item);
my $string = "$fname\n";
if (is_info $item) {
$string= "\@info $string";
}
if (defined $newdir->{$item}) {
if (defined $annotated_dir->{$fname}) {
for my $l (@{$annotated_dir->{$fname}}) {
if (@$l == 4) {
$l->[1]->print("\@comment $fname/\n");
} else {
$l->[1]->print("$fname/\n");
}
}
} else {
$out{$plist}->print("$fname/\n");
}
} else {
my $reported = 0;
if (defined $annotated->{$fname}) {
for my $l (@{$annotated->{$fname}}) {
next if $is_lib && !$l->[2];
if (@$l == 4) {
$l->[1]->print("\@comment $string");
} elsif (@$l == 3) {
$l->[1]->print($string);
} else {
my ($outname, $fh, $is_shared, $mode, $owner, $group, $nocheck) = @$l;
if ($mode ne '') {
$string="\@mode $mode\n$string\@mode\n";
}
if ($owner ne '') {
$string="\@owner $owner\n$string\@owner\n";
}
if ($group ne '') {
$string="\@group $group\n$string\@group\n";
}
if ($nocheck ne '') {
$string="\@comment no checksum\n$string";
}
print $fh $string;
}
$reported = 1;
}
}
print $out $string unless $reported;
}
}
sub may_annotate_and_move
{
my ($p, $is_shared) = @_;
if (-e $p) {
if (defined $out{$p}) {
die "File handle for $p already exists";
}
my $fh = new IO::File "<$p";
rename($p, "$p.orig") or die "Can't rename $p to $p.orig";
my $newfh = new IO::File ">$p";
annotate($fh, $p, $newfh, $is_shared);
close $fh;
$out{$p} = $newfh;
return 1;
} else {
return 0;
}
}
my (%newdir, %occupied, %ldconfig, %has_stuff, @files, @libfiles);
die "Update bsd.port.mk" if @ARGV == 0;
my $plistdir = shift;
die "Update bsd.port.mk" if -f $plistdir;
die "Update bsd.port.mk"
unless defined $ENV{'DEPS'} and defined $ENV{'PKGREPOSITORY'};
die "Update bsd.port.mk"
unless defined $ENV{'PLIST'} and defined $ENV{'PFRAG'};
$plist = $ENV{'PLIST'};
$pshared = $ENV{'PFRAG'}.'.shared';
if (-e "$plist.orig" or -e "$pshared.orig") {
die "You must clean up old files first";
}
my $destdir = $ENV{DESTDIR};
my $mtree = parse_mtree($destdir);
# and directories for dependencies as well
my $tmpdir = tempdir( CLEANUP => 1);
chdir($tmpdir);
for my $pkg (split(/\s+/, $ENV{'DEPS'})) {
augment_mtree($mtree, $destdir, $ENV{'PKGREPOSITORY'}."/$pkg.tgz");
}
may_annotate_and_move($plist, 0);
may_annotate_and_move($pshared, 1) and push(@has_shared, $out{$plist});
# Subpackage rules... better way would be to ask bsd.port.mk directly
my $plist2 = $plist;
$plist2 =~ s/PLIST.*$/PLIST/;
my $multi = $ENV{'MULTI_PACKAGES'};
# Normalize
$multi =~ s/^\s+//;
$multi =~ s/\s+$//;
unless ($multi eq '') {
for my $sub (split(/\s+/, $multi)) {
may_annotate_and_move("$plist$sub", 0) or
may_annotate_and_move("$plist2$sub", 0);
may_annotate_and_move("$pshared$sub", 1) and push(@has_shared, $out{"$plist$sub"});
}
}
for (@ARGV) {
if (m/\=/) {
my $back = $`;
my $v = $';
push(@backsubst, ["\${$back}", $v]) if $v ne '';
}
}
my ($name, $fh);
while (($name, $fh) = each %out) {
}
# compare all files against those dates
my @date = (stat $ENV{INSTALL_PRE_COOKIE})[9, 10];
my %okay_files=map { $_=>1 } split(/\s+/, $ENV{'OKAY_FILES'});
# check the installation directory, try to make certain there is
# no file or directory outside of $base
find(
sub {
if ($File::Find::name eq $base) {
$File::Find::prune = 1;
return;
}
if (-d $_) {
return if $File::Find::name eq $destdir;
if (defined $mtree->{$File::Find::name}) {
return;
} else {
print STDERR "Bogus directory: $File::Find::name\n";
}
} else {
return if defined $okay_files{$File::Find::name};
print STDERR "Bogus file: $File::Find::name\n";
}
}, $destdir);
# recursive traversal: mark specific `info' dirs, `ldconfig' dirs,
# and potentially modified dirs
find(
sub {
my @cdate = (lstat $_)[9, 10];
if ($cdate[0] >= $date[0] || $cdate[1] >= $date[1]) {
$has_stuff{$File::Find::dir} = 1;
if (-d _) {
$newdir{$File::Find::name} = 1;
} else {
if (is_dir($File::Find::name) || is_subinfo($File::Find::name)) {
return;
}
if (is_library($File::Find::name)) {
$ldconfig{$File::Find::dir} = 1;
push(@libfiles, $File::Find::name);
} else {
push(@files, $File::Find::name);
}
}
} else {
$occupied{$File::Find::dir} = 1;
}
}, $base);
# occupied marks a dir that was already there...
# so all parents had to be around too
for my $d (keys %occupied) {
while ($d ne '') {
delete $newdir{$d} if defined $newdir{$d};
$d =~ s|/.*?/?$||;
}
}
# make sure mtree is removed
for my $d (keys %$mtree) {
delete $newdir{$d}
}
unless (defined $out{$plist}) {
$out{$plist} = new IO::File ">$plist";
$out{$plist}->print("\@comment \$OpenBSD\$\n");
}
for my $d (keys %newdir) {
push @files, $d;
}
for my $f (sort @files) {
handle_file(\%newdir, $f, $out{$plist}, 0);
}
for my $fh (@has_shared) {
$fh->print("\%\%SHARED\%\%\n");
}
if (@libfiles > 0) {
unless (defined $out{$pshared}) {
$out{$pshared} = new IO::File ">$pshared";
$out{$pshared}->print("\@comment \$OpenBSD\$\n");
}
$out{$plist}->print("\%\%SHARED\%\%\n") if @has_shared == 0;
for my $f (sort @libfiles) {
handle_file(\%newdir, $f, $out{$pshared}, 1);
}
for my $d (sort (keys %ldconfig)) {
if (defined $newdir{$d}) {
$out{$pshared}->print("NEWDYNLIBDIR(\%D/", strip($d), ")\n");
} else {
$out{$pshared}->print("DYNLIBDIR(\%D/", strip($d), ")\n");
}
}
}
if ($manual) {
print STDERR "make plist: subst/frag/exec/unexec spotted in original file\n";
print STDERR "\tMay require manual intervention\n";
}