1package ExtUtils::Installed;
2
3use 5.00503;
4use strict;
5#use warnings; # XXX requires 5.6
6use Carp qw();
7use ExtUtils::Packlist;
8use ExtUtils::MakeMaker;
9use Config;
10use File::Find;
11use File::Basename;
12use File::Spec;
13
14my $Is_VMS = $^O eq 'VMS';
15my $DOSISH = ($^O =~ /^(MSWin\d\d|os2|dos|mint)$/);
16
17require VMS::Filespec if $Is_VMS;
18
19use vars qw($VERSION);
20$VERSION = '2.14';
21$VERSION = eval $VERSION;
22
23sub _is_prefix {
24    my ($self, $path, $prefix) = @_;
25    return unless defined $prefix && defined $path;
26
27    if( $Is_VMS ) {
28        $prefix = VMS::Filespec::unixify($prefix);
29        $path   = VMS::Filespec::unixify($path);
30    }
31
32    # Unix path normalization.
33    $prefix = File::Spec->canonpath($prefix);
34
35    return 1 if substr($path, 0, length($prefix)) eq $prefix;
36
37    if ($DOSISH) {
38        $path =~ s|\\|/|g;
39        $prefix =~ s|\\|/|g;
40        return 1 if $path =~ m{^\Q$prefix\E}i;
41    }
42    return(0);
43}
44
45sub _is_doc {
46    my ($self, $path) = @_;
47
48    my $man1dir = $self->{':private:'}{Config}{man1direxp};
49    my $man3dir = $self->{':private:'}{Config}{man3direxp};
50    return(($man1dir && $self->_is_prefix($path, $man1dir))
51           ||
52           ($man3dir && $self->_is_prefix($path, $man3dir))
53           ? 1 : 0)
54}
55
56sub _is_type {
57    my ($self, $path, $type) = @_;
58    return 1 if $type eq "all";
59
60    return($self->_is_doc($path)) if $type eq "doc";
61    my $conf= $self->{':private:'}{Config};
62    if ($type eq "prog") {
63        return($self->_is_prefix($path, $conf->{prefix} || $conf->{prefixexp})
64               && !($self->_is_doc($path)) ? 1 : 0);
65    }
66    return(0);
67}
68
69sub _is_under {
70    my ($self, $path, @under) = @_;
71    $under[0] = "" if (! @under);
72    foreach my $dir (@under) {
73        return(1) if ($self->_is_prefix($path, $dir));
74    }
75
76    return(0);
77}
78
79sub _fix_dirs {
80    my ($self, @dirs)= @_;
81    # File::Find does not know how to deal with VMS filepaths.
82    if( $Is_VMS ) {
83        $_ = VMS::Filespec::unixify($_)
84            for @dirs;
85    }
86
87    if ($DOSISH) {
88        s|\\|/|g for @dirs;
89    }
90    return wantarray ? @dirs : $dirs[0];
91}
92
93sub _make_entry {
94    my ($self, $module, $packlist_file, $modfile)= @_;
95
96    my $data= {
97        module => $module,
98        packlist => scalar(ExtUtils::Packlist->new($packlist_file)),
99        packlist_file => $packlist_file,
100    };
101
102    if (!$modfile) {
103        $data->{version} = $self->{':private:'}{Config}{version};
104    } else {
105        $data->{modfile} = $modfile;
106        # Find the top-level module file in @INC
107        $data->{version} = '';
108        foreach my $dir (@{$self->{':private:'}{INC}}) {
109            my $p = File::Spec->catfile($dir, $modfile);
110            if (-r $p) {
111                $module = _module_name($p, $module) if $Is_VMS;
112
113                $data->{version} = MM->parse_version($p);
114                $data->{version_from} = $p;
115                $data->{packlist_valid} = exists $data->{packlist}{$p};
116                last;
117            }
118        }
119    }
120    $self->{$module}= $data;
121}
122
123our $INSTALLED;
124sub new {
125    my ($class) = shift(@_);
126    $class = ref($class) || $class;
127
128    my %args = @_;
129
130    return $INSTALLED if $INSTALLED and ($args{default_get} || $args{default});
131
132    my $self = bless {}, $class;
133
134    $INSTALLED= $self if $args{default_set} || $args{default};
135
136
137    if ($args{config_override}) {
138        eval {
139            $self->{':private:'}{Config} = { %{$args{config_override}} };
140        } or Carp::croak(
141            "The 'config_override' parameter must be a hash reference."
142        );
143    }
144    else {
145        $self->{':private:'}{Config} = \%Config;
146    }
147
148    for my $tuple ([inc_override => INC => [ @INC ] ],
149                   [ extra_libs => EXTRA => [] ])
150    {
151        my ($arg,$key,$val)=@$tuple;
152        if ( $args{$arg} ) {
153            eval {
154                $self->{':private:'}{$key} = [ @{$args{$arg}} ];
155            } or Carp::croak(
156                "The '$arg' parameter must be an array reference."
157            );
158        }
159        elsif ($val) {
160            $self->{':private:'}{$key} = $val;
161        }
162    }
163    {
164        my %dupe;
165        @{$self->{':private:'}{LIBDIRS}} =
166            grep { $_ ne '.' || ! $args{skip_cwd} }
167            grep { -e $_ && !$dupe{$_}++ }
168            @{$self->{':private:'}{EXTRA}}, @{$self->{':private:'}{INC}};
169    }
170
171    my @dirs= $self->_fix_dirs(@{$self->{':private:'}{LIBDIRS}});
172
173    # Read the core packlist
174    my $archlib = $self->_fix_dirs($self->{':private:'}{Config}{archlibexp});
175    $self->_make_entry("Perl",File::Spec->catfile($archlib, '.packlist'));
176
177    my $root;
178    # Read the module packlists
179    my $sub = sub {
180        # Only process module .packlists
181        return if $_ ne ".packlist" || $File::Find::dir eq $archlib;
182
183        # Hack of the leading bits of the paths & convert to a module name
184        my $module = $File::Find::name;
185        my $found = $module =~ s!^.*?/auto/(.*)/.packlist!$1!s
186            or do {
187            # warn "Woah! \$_=$_\n\$module=$module\n\$File::Find::dir=$File::Find::dir\n",
188            #    join ("\n",@dirs);
189            return;
190        };
191
192        my $modfile = "$module.pm";
193        $module =~ s!/!::!g;
194
195        return if $self->{$module}; #shadowing?
196        $self->_make_entry($module,$File::Find::name,$modfile);
197    };
198    while (@dirs) {
199        $root= shift @dirs;
200        next if !-d $root;
201        find($sub,$root);
202    }
203
204    return $self;
205}
206
207# VMS's non-case preserving file-system means the package name can't
208# be reconstructed from the filename.
209sub _module_name {
210    my($file, $orig_module) = @_;
211
212    my $module = '';
213    if (open PACKFH, $file) {
214        while (<PACKFH>) {
215            if (/package\s+(\S+)\s*;/) {
216                my $pack = $1;
217                # Make a sanity check, that lower case $module
218                # is identical to lowercase $pack before
219                # accepting it
220                if (lc($pack) eq lc($orig_module)) {
221                    $module = $pack;
222                    last;
223                }
224            }
225        }
226        close PACKFH;
227    }
228
229    print STDERR "Couldn't figure out the package name for $file\n"
230      unless $module;
231
232    return $module;
233}
234
235sub modules {
236    my ($self) = @_;
237    $self= $self->new(default=>1) if !ref $self;
238
239    # Bug/feature of sort in scalar context requires this.
240    return wantarray
241        ? sort grep { not /^:private:$/ } keys %$self
242        : grep { not /^:private:$/ } keys %$self;
243}
244
245sub files {
246    my ($self, $module, $type, @under) = @_;
247    $self= $self->new(default=>1) if !ref $self;
248
249    # Validate arguments
250    Carp::croak("$module is not installed") if (! exists($self->{$module}));
251    $type = "all" if (! defined($type));
252    Carp::croak('type must be "all", "prog" or "doc"')
253        if ($type ne "all" && $type ne "prog" && $type ne "doc");
254
255    my (@files);
256    foreach my $file (keys(%{$self->{$module}{packlist}})) {
257        push(@files, $file)
258          if ($self->_is_type($file, $type) &&
259              $self->_is_under($file, @under));
260    }
261    return(@files);
262}
263
264sub directories {
265    my ($self, $module, $type, @under) = @_;
266    $self= $self->new(default=>1) if !ref $self;
267    my (%dirs);
268    foreach my $file ($self->files($module, $type, @under)) {
269        $dirs{dirname($file)}++;
270    }
271    return sort keys %dirs;
272}
273
274sub directory_tree {
275    my ($self, $module, $type, @under) = @_;
276    $self= $self->new(default=>1) if !ref $self;
277    my (%dirs);
278    foreach my $dir ($self->directories($module, $type, @under)) {
279        $dirs{$dir}++;
280        my ($last) = ("");
281        while ($last ne $dir) {
282            $last = $dir;
283            $dir = dirname($dir);
284            last if !$self->_is_under($dir, @under);
285            $dirs{$dir}++;
286        }
287    }
288    return(sort(keys(%dirs)));
289}
290
291sub validate {
292    my ($self, $module, $remove) = @_;
293    $self= $self->new(default=>1) if !ref $self;
294    Carp::croak("$module is not installed") if (! exists($self->{$module}));
295    return($self->{$module}{packlist}->validate($remove));
296}
297
298sub packlist {
299    my ($self, $module) = @_;
300    $self= $self->new(default=>1) if !ref $self;
301    Carp::croak("$module is not installed") if (! exists($self->{$module}));
302    return($self->{$module}{packlist});
303}
304
305sub version {
306    my ($self, $module) = @_;
307    $self= $self->new(default=>1) if !ref $self;
308    Carp::croak("$module is not installed") if (! exists($self->{$module}));
309    return($self->{$module}{version});
310}
311
312sub debug_dump {
313    my ($self, $module) = @_;
314    $self= $self->new(default=>1) if !ref $self;
315    local $self->{":private:"}{Config};
316    require Data::Dumper;
317    print Data::Dumper->new([$self])->Sortkeys(1)->Indent(1)->Dump();
318}
319
320
3211;
322
323__END__
324
325=head1 NAME
326
327ExtUtils::Installed - Inventory management of installed modules
328
329=head1 SYNOPSIS
330
331   use ExtUtils::Installed;
332   my ($inst) = ExtUtils::Installed->new( skip_cwd => 1 );
333   my (@modules) = $inst->modules();
334   my (@missing) = $inst->validate("DBI");
335   my $all_files = $inst->files("DBI");
336   my $files_below_usr_local = $inst->files("DBI", "all", "/usr/local");
337   my $all_dirs = $inst->directories("DBI");
338   my $dirs_below_usr_local = $inst->directory_tree("DBI", "prog");
339   my $packlist = $inst->packlist("DBI");
340
341=head1 DESCRIPTION
342
343ExtUtils::Installed  provides a standard way to find out what core and module
344files have been installed.  It uses the information stored in .packlist files
345created during installation to provide this information.  In addition it
346provides facilities to classify the installed files and to extract directory
347information from the .packlist files.
348
349=head1 USAGE
350
351The new() function searches for all the installed .packlists on the system, and
352stores their contents. The .packlists can be queried with the functions
353described below. Where it searches by default is determined by the settings found
354in C<%Config::Config>, and what the value is of the PERL5LIB environment variable.
355
356=head1 METHODS
357
358Unless specified otherwise all method can be called as class methods, or as object
359methods. If called as class methods then the "default" object will be used, and if
360necessary created using the current processes %Config and @INC.  See the
361'default' option to new() for details.
362
363
364=over 4
365
366=item new()
367
368This takes optional named parameters. Without parameters, this
369searches for all the installed .packlists on the system using
370information from C<%Config::Config> and the default module search
371paths C<@INC>. The packlists are read using the
372L<ExtUtils::Packlist> module.
373
374If the named parameter C<skip_cwd> is true, the current directory C<.> will
375be stripped from C<@INC> before searching for .packlists.  This keeps
376ExtUtils::Installed from finding modules installed in other perls that
377happen to be located below the current directory.
378
379If the named parameter C<config_override> is specified,
380it should be a reference to a hash which contains all information
381usually found in C<%Config::Config>. For example, you can obtain
382the configuration information for a separate perl installation and
383pass that in.
384
385    my $yoda_cfg  = get_fake_config('yoda');
386    my $yoda_inst =
387               ExtUtils::Installed->new(config_override=>$yoda_cfg);
388
389Similarly, the parameter C<inc_override> may be a reference to an
390array which is used in place of the default module search paths
391from C<@INC>.
392
393    use Config;
394    my @dirs = split(/\Q$Config{path_sep}\E/, $ENV{PERL5LIB});
395    my $p5libs = ExtUtils::Installed->new(inc_override=>\@dirs);
396
397B<Note>: You probably do not want to use these options alone, almost always
398you will want to set both together.
399
400The parameter C<extra_libs> can be used to specify B<additional> paths to
401search for installed modules. For instance
402
403    my $installed =
404             ExtUtils::Installed->new(extra_libs=>["/my/lib/path"]);
405
406This should only be necessary if F</my/lib/path> is not in PERL5LIB.
407
408Finally there is the 'default', and the related 'default_get' and 'default_set'
409options. These options control the "default" object which is provided by the
410class interface to the methods. Setting C<default_get> to true tells the constructor
411to return the default object if it is defined. Setting C<default_set> to true tells
412the constructor to make the default object the constructed object. Setting the
413C<default> option is like setting both to true. This is used primarily internally
414and probably isn't interesting to any real user.
415
416=item modules()
417
418This returns a list of the names of all the installed modules.  The perl 'core'
419is given the special name 'Perl'.
420
421=item files()
422
423This takes one mandatory parameter, the name of a module.  It returns a list of
424all the filenames from the package.  To obtain a list of core perl files, use
425the module name 'Perl'.  Additional parameters are allowed.  The first is one
426of the strings "prog", "doc" or "all", to select either just program files,
427just manual files or all files.  The remaining parameters are a list of
428directories. The filenames returned will be restricted to those under the
429specified directories.
430
431=item directories()
432
433This takes one mandatory parameter, the name of a module.  It returns a list of
434all the directories from the package.  Additional parameters are allowed.  The
435first is one of the strings "prog", "doc" or "all", to select either just
436program directories, just manual directories or all directories.  The remaining
437parameters are a list of directories. The directories returned will be
438restricted to those under the specified directories.  This method returns only
439the leaf directories that contain files from the specified module.
440
441=item directory_tree()
442
443This is identical in operation to directories(), except that it includes all the
444intermediate directories back up to the specified directories.
445
446=item validate()
447
448This takes one mandatory parameter, the name of a module.  It checks that all
449the files listed in the modules .packlist actually exist, and returns a list of
450any missing files.  If an optional second argument which evaluates to true is
451given any missing files will be removed from the .packlist
452
453=item packlist()
454
455This returns the ExtUtils::Packlist object for the specified module.
456
457=item version()
458
459This returns the version number for the specified module.
460
461=back
462
463=head1 EXAMPLE
464
465See the example in L<ExtUtils::Packlist>.
466
467=head1 AUTHOR
468
469Alan Burlison <Alan.Burlison@uk.sun.com>
470
471=cut
472