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