#! /usr/bin/perl # $OpenBSD: make-plist,v 1.117 2008/10/28 13:29:01 espie Exp $ # Copyright (c) 2004-2008 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # TODO # - multi-package with conflicts don't work. # (need to multi annotate files) # - multi-packages with inter-dependencies incorrectly strip dirs # (need to strip dirs in a smarter way ???) # - sample dir/ gets added at the wrong location. use strict; use warnings; use lib $ENV{PORTSDIR}."/infrastructure/install"; use OpenBSD::PackingList; use OpenBSD::PackingElement; use OpenBSD::PackageLocator; use OpenBSD::PackageInfo; use OpenBSD::Subst; use File::Basename; use File::Compare; use File::Temp; use FS; package OpenBSD::ReverseSubst; our @ISA = (qw(OpenBSD::Subst)); sub new { bless {h => {}, r => [], l => {}}, shift; } sub hash { my $self = shift; return $self->{h}; } sub value { my ($self, $k) = @_; return $self->{h}->{$k}; } sub add { my ($self, $k, $v) = @_; if ($k =~ m/^LIB(.*)_VERSION$/) { $self->{l}->{$1} = $v; } else { push(@{$self->{r}}, $k) if $v ne ''; } $k =~ s/^\^//; $self->{h}->{$k} = $v; } sub reverse { my ($self, $_) = @_; for my $k (@{$self->{r}}) { if ($k =~ m/^\^(.*)$/) { $k = $1; my $v = $self->{h}->{$k}; s/^\Q$v\E/\$\{\Q$k\E\}/g; } else { my $v = $self->{h}->{$k}; s/\Q$v\E/\$\{\Q$k\E\}/g; } } return $_; } sub reverse_with_lib { my ($self, $_) = @_; if (m/^(.*?)lib([^\/]+)\.so\.(\d+\.\d+)$/) { my ($path, $name, $version) = ($1, $2, $3); if (!defined $self->{l}->{$name}) { print STDERR "WARNING: unregistered shared lib: $name " . "(version: $version)\n"; $self->{l}->{$name} = $version; } elsif ($self->{l}->{$name} ne $version) { print STDERR "WARNING: version mismatch for lib: $name " . "($version vs. $self->{l}->{$name})\n"; } return $self->reverse("${path}lib$name.so.")."\${LIB${name}_VERSION}"; } else { return $self->reverse($_); } } package main; # Plists use variable substitution, we have to be able to do it # both ways to recognize existing entries. my $base; our $subst = new OpenBSD::ReverseSubst; my $destdir = $ENV{'DESTDIR'}; my %known_libs; die "No $destdir" unless -d $destdir; my %prefix; my %plistname; my %mtree; my @subs; my $baseprefix=$ENV{PREFIX}; my $shared_only; my $make = $ENV{MAKE}; my $portsdir = $ENV{PORTSDIR}; my $cached_tree = {}; sub build_mtree { my ($sub, $deps) = @_; my $mtree = {}; # add directories from dependencies my $stripped = {}; for my $pkgpath (split /\s+/, $deps) { next if defined $stripped->{$pkgpath}; $stripped->{$pkgpath} = 1; if (!defined $cached_tree->{$pkgpath}) { $cached_tree->{$pkgpath} = {}; open my $fh, "cd $portsdir && env -i SUBDIR=$pkgpath ECHO_MSG=: $make print-plist |" or die "blech\n"; augment_mtree($cached_tree->{$pkgpath}, $fh); close($fh); } print STDERR "Subpackage $sub: Stripping dirs from $pkgpath\n"; for my $e (keys %{$cached_tree->{$pkgpath}}) { $mtree->{$e} = 1; } } return $mtree; } sub parse_arg { my $_ = shift; if (m/^DEPPATHS(-.*?)\=/) { $mtree{$1} = build_mtree($1, $'); return; } if (m/\=/) { $subst->parse_option($_); } if (m/^\^PREFIX(\-.*?)\=(.*)\/?$/) { $prefix{$1} = $2; } elsif (m/^PLIST(\-.*?)\=/) { $plistname{$1} = $'; } } sub parse_env { } sub parse_args { for my $i (@ARGV) { parse_arg($i); } my $multi = $ENV{'MULTI_PACKAGES'}; # Normalize $multi =~ s/^\s+//; $multi =~ s/\s+$//; @subs = split /\s+/, $multi; for my $sub (@subs) { if (!defined $prefix{$sub} || !defined $plistname{$sub} || !defined $mtree{$sub}) { die "Incomplete information for $sub"; } } if (defined $ENV{'SHARED_ONLY'}) { if ($ENV{'SHARED_ONLY'} =~ m/^Yes$/i) { $shared_only = 1; } } } sub deduce_name { my ($o, $frag, $not) = @_; my $noto = $o; my $nofrag = "no-$frag"; $o =~ s/PFRAG\./PFRAG.$frag-/ or $o =~ s/PLIST/PFRAG.$frag/; $noto =~ s/PFRAG\./PFRAG.no-$frag-/ or $noto =~ s/PLIST/PFRAG.no-$frag/; if ($not) { return $noto; } else { return $o; } } sub possible_subpackages { my $filename= shift; my $l = []; for my $sub (@subs) { if ($filename =~ m/^\Q$prefix{$sub}\E\//) { push @$l, $sub; } } return $l; } # Fragments are new PackingElement unique to make-plist and pkg_create, # to handle %%thingy%%. # (and so, make-plist will use a special PLIST reader) # Method summary: # add_to_mtree: new directory in dependent package # register: known items and known comments # copy_extra: stuff that can't be easily deduced but should be copied # tag_along: set of items that associate themselves to this item # (e.g., @exec, @unexec, @sample...) # clone_tags: copy tagged stuff over. # deduce_fragment: find fragment file name from %%stuff%% # note $plist->{nonempty}: set as soon as a plist holds anything # but a cvstag. package OpenBSD::PackingElement; sub add_to_mtree { } sub register2 { my ($self, $plist, $haystack) = @_; $self->{plist} = $plist; } sub register { } sub copy_extra { } sub tag_along { my ($self, $n) = @_; $self->{tags} = [] unless defined $self->{tags}; push(@{$self->{tags}}, $n); } sub deduce_fragment { } sub clone_tags { my ($self, $plist) = @_; if (defined $self->{tags}) { for my $t (@{$self->{tags}}) { my $n = $t->clone; if ($n->isa("OpenBSD::PackingElement::Sample") || $n->isa("OpenBSD::PackingElement::SampleDir")) { main::handle_modes($plist, $n, $t); } $n->add_object($plist); $plist->{nonempty} = 1; if ($n->isa("OpenBSD::PackingElement::Fragment") && $n->{name} eq "SHARED") { $plist->{hasshared} = 1; } } } } sub copy_annotations { } package OpenBSD::PackingElement::CVSTag; sub copy_annotations { my ($self, $plist) = @_; $self->clone->add_object($plist); } package OpenBSD::PackingElement::NoDefaultConflict; sub copy_annotations { my ($self, $plist) = @_; $self->clone->add_object($plist); $plist->{nonempty} = 1; } package OpenBSD::PackingElement::Conflict; sub copy_annotations { &OpenBSD::PackingElement::NoDefaultConflict::copy_annotations; } package OpenBSD::PackingElement::NewAuth; sub copy_annotations { &OpenBSD::PackingElement::NoDefaultConflict::copy_annotations; } package OpenBSD::PackingElement::PkgPath; sub copy_annotations { &OpenBSD::PackingElement::NoDefaultConflict::copy_annotations; } package OpenBSD::PackingElement::Incompatibility; sub copy_annotations { &OpenBSD::PackingElement::NoDefaultConflict::copy_annotations; } package OpenBSD::PackingElement::UpdateSet; sub copy_annotations { &OpenBSD::PackingElement::NoDefaultConflict::copy_annotations; } package OpenBSD::PackingElement::Fragment; our @ISA=qw(OpenBSD::PackingElement); sub register { my ($self, $plist) = @_; if (defined $plist->{state}->{lastobject}) { $plist->{state}->{lastobject}->tag_along($self); } else { $plist->{tag_marker}->tag_along($self); } } sub deduce_fragment { my ($self, $o) = @_; my $frag = $self->{name}; return if $frag eq "SHARED"; $o =~ s/PFRAG\./PFRAG.$frag-/ or $o =~ s/PLIST/PFRAG.$frag/; return $o if -e $o; } sub needs_keyword() { 0 } sub stringize { return '%%'.shift->{name}.'%%'; } package OpenBSD::PackingElement::NoFragment; our @ISA=qw(OpenBSD::PackingElement::Fragment); sub deduce_fragment { my ($self, $noto) = @_; my $frag = $self->{name}; return if $frag eq "SHARED"; $noto =~ s/PFRAG\./PFRAG.no-$frag-/ or $noto =~ s/PLIST/PFRAG.no-$frag/; return $noto if -e $noto; } sub stringize { return '!%%'.shift->{name}.'%%'; } package OpenBSD::PackingElement::FileObject; sub register2 { my ($self, $plist, $haystack) = @_; $self->SUPER::register2($plist, $haystack); my $fullname = $self->fullname; my $n = $main::subst->do($fullname); push(@{$haystack->{$n}}, $self); } package OpenBSD::PackingElement::FileBase; sub register { my ($self, $plist) = @_; $plist->{state}->{lastobject} = $self; } package OpenBSD::PackingElement::Dir; sub register { my ($self, $plist) = @_; $plist->{state}->{lastobject} = $self; } package OpenBSD::PackingElement::Sample; sub register { my ($self, $plist) = @_; if (defined $self->{copyfrom}) { $self->{copyfrom}->tag_along($self); } else { print "Bogus sample (unattached) detected\n"; } } package OpenBSD::PackingElement::Sysctl; sub register { my ($self, $plist) = @_; if (defined $plist->{state}->{lastobject}) { $plist->{state}->{lastobject}->tag_along($self); } else { $plist->{tag_marker}->tag_along($self); } } package OpenBSD::PackingElement::ExeclikeAction; sub register { my ($self, $plist) = @_; if (defined $plist->{state}->{lastobject}) { $plist->{state}->{lastobject}->tag_along($self); } else { $plist->{tag_marker}->tag_along($self); } } package OpenBSD::PackingElement::Sampledir; sub register { my ($self, $plist) = @_; if (defined $plist->{state}->{lastobject}) { $plist->{state}->{lastobject}->tag_along($self); } else { $plist->{tag_marker}->tag_along($self); } } package OpenBSD::PackingElement::DirlikeObject; sub add_to_mtree { my ($self, $mtree) = @_; $mtree->{$self->fullname} = 1; } package OpenBSD::PackingElement::Comment; sub cwd { return ${$_[0]->{cwd}}; } sub register2 { my ($self, $plist, $haystack) = @_; $self->SUPER::register2($plist, $haystack); my $fullname = File::Spec->canonpath($self->{name}); if ($fullname !~ m|^/|o && $self->cwd ne '.') { $fullname = $self->cwd."/".$fullname; } my $n = $main::subst->do($fullname); push(@{$haystack->{$n}}, $self); } package OpenBSD::PackingElement::Extra; sub copy_extra { my ($self, $plist) = @_; if ($self->cwd ne $plist->{state}->cwd) { OpenBSD::PackingElement::Cwd->add($plist, $self->cwd); } $self->clone->add_object($plist); $plist->{nonempty} = 1; } package OpenBSD::PackingElement::ExtraUnexec; sub copy_extra { my ($self, $plist) = @_; # don't try to deal with cwd issues $self->clone->add_object($plist); $plist->{nonempty} = 1; } sub register { } package main; # add dependent package directories to the set of directories that don't # need registration. sub augment_mtree { my ($mtree, $fh) = @_; my $plist = OpenBSD::PackingList->read($fh, \&OpenBSD::PackingList::SharedItemsOnly) or die "couldn't read packing-list\n"; $plist->add_to_mtree($mtree); } my $haystack = {}; # Basic packing-list with a known prefix sub create_packinglist { my ($filename, $sub) = @_; my $prefix = $prefix{$sub}; my $plist = new OpenBSD::PackingList; $plist->{filename} = $filename; $plist->{mtree} = $mtree{$sub}; $plist->{state}->set_cwd($prefix); $prefix.='/' unless $prefix =~ m|/$|; $plist->{stripprefix} = $prefix; return $plist; } # grab original packing list, killing some stuff that is no longer needed. sub parse_original_plist { my ($name, $sub, $all_plists) = @_; my $plist = create_packinglist($name, $sub); # place holder for extra stuff that comes before any file $plist->{tag_marker} = new OpenBSD::PackingElement(''); # special reader for fragments $plist->fromfile($name, sub { my ($fh, $cont) = @_; my $_; while (<$fh>) { if (m/^\%\%(.*)\%\%$/) { OpenBSD::PackingElement::Fragment->add($plist, $1); } elsif (m/^\!\%\%(.*)\%\%$/) { OpenBSD::PackingElement::NoFragment->add($plist, $1); } else { &$cont($_); } } } ) or return; delete $plist->{state}->{lastobject}; $plist->register($plist); $plist->register2($plist, $haystack); # Try to handle fragments for my $item (@{$plist->{items}}) { my $fragname = $item->deduce_fragment($name); next unless defined $fragname; my $pfrag = create_packinglist($fragname, $sub); $pfrag->{isfrag} = 1; push(@$all_plists, $pfrag); my $origpfrag = parse_original_plist($fragname, $sub, $all_plists); replaces($origpfrag, $pfrag); } return $plist; } # link original and new plist sub replaces { my ($orig, $n) = @_; if (defined $orig) { $n->{original} = $orig; $orig->{replacement} = $n; $n->{filename} = $orig->{filename}; $orig->{tag_marker}->clone_tags($n); } } sub grab_all_lists { my $l = []; for my $sub (@subs) { my $o; my $n = create_packinglist($plistname{$sub}, $sub); push(@$l, $n); $o = parse_original_plist($plistname{$sub}, $sub, $l); replaces($o, $n); my $frag = deduce_name($plistname{$sub}, "shared", 0); my $ns = create_packinglist($frag, $sub); $n->{shared} = $ns; $o = parse_original_plist($frag, $sub, $l); replaces($o, $ns); push(@$l, $ns); } return @$l; } # new object according to type, just copy over some stuff for now sub create_object { my ($type, $short, $item) = @_; if (defined $item && $item->isa("OpenBSD::PackingElement::Comment")) { return OpenBSD::PackingElement::Comment->new($short); } if ($type eq "directory") { if (defined $item) { if ($item->isa("OpenBSD::PackingElement::Mandir")) { return OpenBSD::PackingElement::Mandir->new($short); } elsif ($item->isa("OpenBSD::PackingElement::Fontdir")) { return OpenBSD::PackingElement::Fontdir->new($short); } } return OpenBSD::PackingElement::Dir->new($short); } elsif ($type eq "manpage") { return OpenBSD::PackingElement::Manpage->new($short); } elsif ($type eq "dir" || $type eq "subinfo") { return undef; } elsif ($type eq "info") { return OpenBSD::PackingElement::InfoFile->new($short); } elsif ($type eq "library") { return OpenBSD::PackingElement::Lib->new($short); } elsif ($type eq "binary") { return OpenBSD::PackingElement::Binary->new($short); } else { if (defined $item) { if ($item->isa("OpenBSD::PackingElement::Shell")) { return OpenBSD::PackingElement::Shell->new($short); } } return OpenBSD::PackingElement::File->new($short); } } # `restate' packing-list according to current mode settings. # for now, we copy over stuff from old items. sub handle_modes { my ($plist, $item, $o) = @_; my ($mode, $owner, $group) = ('', '', ''); my ($oldmode, $oldowner, $oldgroup) = ($plist->{state}->{mode}, $plist->{state}->{owner}, $plist->{state}->{group}); $oldmode = '' unless defined $oldmode; $oldowner = '' unless defined $oldowner; $oldgroup = '' unless defined $oldgroup; if (defined $item) { if (defined $item->{nochecksum}) { $o->{nochecksum} = 1; } if (defined $item->{ignore}) { $o->{ignore} = 1; } if (defined $item->{mode}) { $mode = $item->{mode}; } if (defined $item->{owner}) { $owner = $item->{owner}; } if (defined $item->{group}) { $group = $item->{group}; } } if ($mode ne $oldmode) { OpenBSD::PackingElement::Mode->add($plist, $mode); } if ($owner ne $oldowner) { OpenBSD::PackingElement::Owner->add($plist, $owner); } if ($group ne $oldgroup) { OpenBSD::PackingElement::Group->add($plist, $group); } } sub short_name { my ($file, $plist) = @_; my $short = $file->path; my $base = $plist->{stripprefix}; if ($short =~ m/^\Q$base\E/) { $short = $'; $short = '/' if $short eq ''; } else { return undef; } if ($file->type eq 'library') { $short = $subst->reverse_with_lib($short); } else { $short = $subst->reverse($short); } # If the resulting name is arch-dependent, we warn. # We don't fix it automatically, as this may need special handling. if ($short =~ m/i386|m68k|sparc/) { print STDERR "make-plist: generated plist $plist->{filename} contains arch-dependent\n"; print STDERR "\t$short\n"; } return $short; } sub bad_files { my ($short, $plist) = @_; if ($short =~ /\.orig$/) { print STDERR "make-plist: generated plist $plist->{filename} may contain patched file\n"; print STDERR "\t$short\n"; } if ($short =~ /\/\.[^\/]*\.swp$/) { print STDERR "make-plist: generated plist $plist->{filename} may contain vim swap file\n"; print STDERR "\t$short\n"; } if ($short =~ /\~$/) { print STDERR "make-plist: generated plist $plist->{filename} may contain emacs temp file\n"; print STDERR "\t$short\n"; } } # find out where a file belongs, and insert all corresponding things # into the right packing-list. sub handle_file { my ($file, $haystack, $allplists, $shared_only) = @_; my $foundit; my $default = $allplists->[0]; if (defined $haystack->{$file->path}) { for my $item (@{$haystack->{$file->path}}) { my $p = $item->{plist}->{replacement}; if ($file->type eq 'directory' && $p->{mtree}->{$file->path}) { print "This should not happen !!!\n"; next; } my $short = short_name($file, $p); my $o = create_object($file->type, $short, $item); if (!defined $o) { next; } $foundit = $item; if ($o->can("compute_modes")) { handle_modes($p, $item, $o); } $o->add_object($p); $item->{accounted_for} = $o; $p->{nonempty} = 1; # Copy properties from source item $item->clone_tags($p); } } if (defined $foundit) { return; } # Try to find a directory that `works' my $dir = $file->path; while (($dir = dirname($dir)) ne '/') { if (defined $haystack->{$dir} && @{$haystack->{$dir}} eq 1) { my $p = $haystack->{$dir}[0]->{plist}->{replacement}; my $short = short_name($file, $p); my $o = create_object($file->type, $short, undef); if (!defined $o) { next; } if ($o->can("compute_modes")) { handle_modes($p, undef, $o); } $o->add_object($p); $p->{nonempty} = 1; return; } } my $short; my $p; my $possible = possible_subpackages($file->path); if (@$possible == 0) { print "Bogus element outside of every prefix: ", $file->path, "\n"; return; } # look for the first matching prefix in plist to produce an entry for my $try (@$allplists) { if ($file->type eq 'directory' and $try->{mtree}->{$file->path}) { next; } $short = short_name($file, $try); if (defined $short) { $p = $try; if ($p ne $default) { print "Element ", $file->path, " going to ", $p->{filename}, " based on prefix\n"; } last; } } if (!defined $p) { return; } my $o = create_object($file->type, $short, undef); return unless defined $o; bad_files($short, $p); if (($file->type eq 'library' || $file->type eq 'plugin') && !$shared_only) { $p->{wantshared} = 1; $p = $p->{shared}; } handle_modes($p, undef, $o); $o->add_object($p); $p->{nonempty} = 1; } # THIS IS WHERE THE MAIN PROGRAM STARTS parse_args(); my $files = FS::get_files($destdir); my @l = grab_all_lists(); for my $plist (@l) { my $orig = $plist->{original}; if (defined $orig) { $orig->copy_annotations($plist); } if (!$plist->has('cvstags')) { OpenBSD::PackingElement::CVSTag->add($plist, '$OpenBSD'.'$'); } } #for my $i (sort keys %$files) { # if (defined $haystack->{$i}) { # print "$i: \n"; # for my $item (@{$haystack->{$i}}) { # print "\t", ref($item), " ", $item->{plist}->{filename},"\n"; # } # } else { # print "$i NOT FOUND\n"; # } #} for my $i (sort keys %$files) { handle_file($files->{$i}, $haystack, \@l, $shared_only); } # Copy extra stuff for my $plist (@l) { my $orig = $plist->{original}; next unless defined $orig; for my $i (@{$orig->{items}}) { $i->copy_extra($plist); } } my $default = $l[0]; if (($default->{wantshared} || (defined $default->{shared}) && $default->{shared}->{nonempty}) && !$default->{hasshared}) { unshift(@{$default->{items}}, OpenBSD::PackingElement::Fragment->new("SHARED")); $default->{nonempty} = 1; } #for my $k (sort keys %$foundcomments) { # next if defined $foundcomments->{$k}->{accounted_for}; # print "Not accounted for: \@comment $k\n"; #} # write new info over, as joe user. # first we write out everything in /tmp # then we signal if something changed # if that's the case, we die if orig files exist, or we copy stuff over. { local ($), $>); if (defined $ENV{'GROUP'}) { $) = $ENV{'GROUP'}; } if (defined $ENV{'OWNER'}) { $> = $ENV{'OWNER'}; } my $dir = File::Temp::tempdir ( CLEANUP => 1); $dir.='/'; # write out everything for my $plist (@l) { if (!$plist->{nonempty}) { next; } $plist->tofile($dir.basename($plist->{filename})); } my $something_changed = 0; for my $plist (@l) { my $orig = $plist->{original}; if ($plist->{nonempty}) { if (defined $orig) { if (compare($dir.basename($plist->{filename}), $orig->{filename}) != 0) { print $plist->{filename}, " changed\n"; $something_changed = 1; $plist->{changed} = 1; } } else { print $plist->{filename}, " is new\n"; $something_changed = 1; $plist->{changed} = 1; } } else { if (defined $orig) { if ($plist->{isfrag}) { print $plist->{filename}, " empty fragment: NOT writing it\n"; } else { print $plist->{filename}, " empty\n"; $something_changed = 1; $plist->{changed} = 1; } } } } my $letsdie = 0; if ($something_changed) { for my $plist (@l) { my $orig = $plist->{original}; if (defined $orig) { if (-e $orig->{filename}.".orig") { print $orig->{filename}.".orig present\n"; $letsdie = 1; } } } } if ($letsdie) { exit(1); } for my $plist (@l) { my $orig = $plist->{original}; if ($plist->{changed}) { if (defined $orig) { rename($orig->{filename}, $orig->{filename}.".orig") or die "Can't rename file ", $orig->{filename}, "\n"; } $plist->tofile($plist->{filename}) or die "Can't write plist: ", $plist->{filename}, "\n"; } } }