1package ExtUtils::Packlist; 2use strict; 3 4use Carp qw(); 5use Config; 6our $Relocations; 7our $VERSION = '2.22'; 8$VERSION = eval $VERSION; 9 10# Used for generating filehandle globs. IO::File might not be available! 11my $fhname = "FH1"; 12 13=begin _undocumented 14 15=over 16 17=item mkfh() 18 19Make a filehandle. Same kind of idea as Symbol::gensym(). 20 21=cut 22 23sub mkfh() 24{ 25no strict; 26local $^W; 27my $fh = \*{$fhname++}; 28use strict; 29return($fh); 30} 31 32=item __find_relocations 33 34Works out what absolute paths in the configuration have been located at run 35time relative to $^X, and generates a regexp that matches them 36 37=back 38 39=end _undocumented 40 41=cut 42 43sub __find_relocations 44{ 45 my %paths; 46 while (my ($raw_key, $raw_val) = each %Config) { 47 my $exp_key = $raw_key . "exp"; 48 next unless exists $Config{$exp_key}; 49 next unless $raw_val =~ m!\.\.\./!; 50 $paths{$Config{$exp_key}}++; 51 } 52 # Longest prefixes go first in the alternatives 53 my $alternations = join "|", map {quotemeta $_} 54 sort {length $b <=> length $a} keys %paths; 55 qr/^($alternations)/o; 56} 57 58sub new($$) 59{ 60my ($class, $packfile) = @_; 61$class = ref($class) || $class; 62my %self; 63tie(%self, $class, $packfile); 64return(bless(\%self, $class)); 65} 66 67sub TIEHASH 68{ 69my ($class, $packfile) = @_; 70my $self = { packfile => $packfile }; 71bless($self, $class); 72$self->read($packfile) if (defined($packfile) && -f $packfile); 73return($self); 74} 75 76sub STORE 77{ 78$_[0]->{data}->{$_[1]} = $_[2]; 79} 80 81sub FETCH 82{ 83return($_[0]->{data}->{$_[1]}); 84} 85 86sub FIRSTKEY 87{ 88my $reset = scalar(keys(%{$_[0]->{data}})); 89return(each(%{$_[0]->{data}})); 90} 91 92sub NEXTKEY 93{ 94return(each(%{$_[0]->{data}})); 95} 96 97sub EXISTS 98{ 99return(exists($_[0]->{data}->{$_[1]})); 100} 101 102sub DELETE 103{ 104return(delete($_[0]->{data}->{$_[1]})); 105} 106 107sub CLEAR 108{ 109%{$_[0]->{data}} = (); 110} 111 112sub DESTROY 113{ 114} 115 116sub read($;$) 117{ 118my ($self, $packfile) = @_; 119$self = tied(%$self) || $self; 120 121if (defined($packfile)) { $self->{packfile} = $packfile; } 122else { $packfile = $self->{packfile}; } 123Carp::croak("No packlist filename specified") if (! defined($packfile)); 124my $fh = mkfh(); 125open($fh, "<$packfile") || Carp::croak("Can't open file $packfile: $!"); 126$self->{data} = {}; 127my ($line); 128while (defined($line = <$fh>)) 129 { 130 chomp $line; 131 my ($key, $data) = $line; 132 if ($key =~ /^(.*?)( \w+=.*)$/) 133 { 134 $key = $1; 135 $data = { map { split('=', $_) } split(' ', $2)}; 136 137 if ($Config{userelocatableinc} && $data->{relocate_as}) 138 { 139 require File::Spec; 140 require Cwd; 141 my ($vol, $dir) = File::Spec->splitpath($packfile); 142 my $newpath = File::Spec->catpath($vol, $dir, $data->{relocate_as}); 143 $key = Cwd::realpath($newpath); 144 } 145 } 146 $key =~ s!/\./!/!g; # Some .packlists have spurious '/./' bits in the paths 147 $self->{data}->{$key} = $data; 148 } 149close($fh); 150} 151 152sub write($;$) 153{ 154my ($self, $packfile) = @_; 155$self = tied(%$self) || $self; 156if (defined($packfile)) { $self->{packfile} = $packfile; } 157else { $packfile = $self->{packfile}; } 158Carp::croak("No packlist filename specified") if (! defined($packfile)); 159my $fh = mkfh(); 160open($fh, ">$packfile") || Carp::croak("Can't open file $packfile: $!"); 161foreach my $key (sort(keys(%{$self->{data}}))) 162 { 163 my $data = $self->{data}->{$key}; 164 if ($Config{userelocatableinc}) { 165 $Relocations ||= __find_relocations(); 166 if ($packfile =~ $Relocations) { 167 # We are writing into a subdirectory of a run-time relocated 168 # path. Figure out if the this file is also within a subdir. 169 my $prefix = $1; 170 if (File::Spec->no_upwards(File::Spec->abs2rel($key, $prefix))) 171 { 172 # The relocated path is within the found prefix 173 my $packfile_prefix; 174 (undef, $packfile_prefix) 175 = File::Spec->splitpath($packfile); 176 177 my $relocate_as 178 = File::Spec->abs2rel($key, $packfile_prefix); 179 180 if (!ref $data) { 181 $data = {}; 182 } 183 $data->{relocate_as} = $relocate_as; 184 } 185 } 186 } 187 print $fh ("$key"); 188 if (ref($data)) 189 { 190 foreach my $k (sort(keys(%$data))) 191 { 192 print $fh (" $k=$data->{$k}"); 193 } 194 } 195 print $fh ("\n"); 196 } 197close($fh); 198} 199 200sub validate($;$) 201{ 202my ($self, $remove) = @_; 203$self = tied(%$self) || $self; 204my @missing; 205foreach my $key (sort(keys(%{$self->{data}}))) 206 { 207 if (! -e $key) 208 { 209 push(@missing, $key); 210 delete($self->{data}{$key}) if ($remove); 211 } 212 } 213return(@missing); 214} 215 216sub packlist_file($) 217{ 218my ($self) = @_; 219$self = tied(%$self) || $self; 220return($self->{packfile}); 221} 222 2231; 224 225__END__ 226 227=head1 NAME 228 229ExtUtils::Packlist - manage .packlist files 230 231=head1 SYNOPSIS 232 233 use ExtUtils::Packlist; 234 my ($pl) = ExtUtils::Packlist->new('.packlist'); 235 $pl->read('/an/old/.packlist'); 236 my @missing_files = $pl->validate(); 237 $pl->write('/a/new/.packlist'); 238 239 $pl->{'/some/file/name'}++; 240 or 241 $pl->{'/some/other/file/name'} = { type => 'file', 242 from => '/some/file' }; 243 244=head1 DESCRIPTION 245 246ExtUtils::Packlist provides a standard way to manage .packlist files. 247Functions are provided to read and write .packlist files. The original 248.packlist format is a simple list of absolute pathnames, one per line. In 249addition, this package supports an extended format, where as well as a filename 250each line may contain a list of attributes in the form of a space separated 251list of key=value pairs. This is used by the installperl script to 252differentiate between files and links, for example. 253 254=head1 USAGE 255 256The hash reference returned by the new() function can be used to examine and 257modify the contents of the .packlist. Items may be added/deleted from the 258.packlist by modifying the hash. If the value associated with a hash key is a 259scalar, the entry written to the .packlist by any subsequent write() will be a 260simple filename. If the value is a hash, the entry written will be the 261filename followed by the key=value pairs from the hash. Reading back the 262.packlist will recreate the original entries. 263 264=head1 FUNCTIONS 265 266=over 4 267 268=item new() 269 270This takes an optional parameter, the name of a .packlist. If the file exists, 271it will be opened and the contents of the file will be read. The new() method 272returns a reference to a hash. This hash holds an entry for each line in the 273.packlist. In the case of old-style .packlists, the value associated with each 274key is undef. In the case of new-style .packlists, the value associated with 275each key is a hash containing the key=value pairs following the filename in the 276.packlist. 277 278=item read() 279 280This takes an optional parameter, the name of the .packlist to be read. If 281no file is specified, the .packlist specified to new() will be read. If the 282.packlist does not exist, Carp::croak will be called. 283 284=item write() 285 286This takes an optional parameter, the name of the .packlist to be written. If 287no file is specified, the .packlist specified to new() will be overwritten. 288 289=item validate() 290 291This checks that every file listed in the .packlist actually exists. If an 292argument which evaluates to true is given, any missing files will be removed 293from the internal hash. The return value is a list of the missing files, which 294will be empty if they all exist. 295 296=item packlist_file() 297 298This returns the name of the associated .packlist file 299 300=back 301 302=head1 EXAMPLE 303 304Here's C<modrm>, a little utility to cleanly remove an installed module. 305 306 #!/usr/local/bin/perl -w 307 308 use strict; 309 use IO::Dir; 310 use ExtUtils::Packlist; 311 use ExtUtils::Installed; 312 313 sub emptydir($) { 314 my ($dir) = @_; 315 my $dh = IO::Dir->new($dir) || return(0); 316 my @count = $dh->read(); 317 $dh->close(); 318 return(@count == 2 ? 1 : 0); 319 } 320 321 # Find all the installed packages 322 print("Finding all installed modules...\n"); 323 my $installed = ExtUtils::Installed->new(); 324 325 foreach my $module (grep(!/^Perl$/, $installed->modules())) { 326 my $version = $installed->version($module) || "???"; 327 print("Found module $module Version $version\n"); 328 print("Do you want to delete $module? [n] "); 329 my $r = <STDIN>; chomp($r); 330 if ($r && $r =~ /^y/i) { 331 # Remove all the files 332 foreach my $file (sort($installed->files($module))) { 333 print("rm $file\n"); 334 unlink($file); 335 } 336 my $pf = $installed->packlist($module)->packlist_file(); 337 print("rm $pf\n"); 338 unlink($pf); 339 foreach my $dir (sort($installed->directory_tree($module))) { 340 if (emptydir($dir)) { 341 print("rmdir $dir\n"); 342 rmdir($dir); 343 } 344 } 345 } 346 } 347 348=head1 AUTHOR 349 350Alan Burlison <Alan.Burlison@uk.sun.com> 351 352=cut 353