summaryrefslogtreecommitdiff
path: root/pkgtools/lintpkgsrc
diff options
context:
space:
mode:
authorabs <abs@pkgsrc.org>2012-07-13 21:12:07 +0000
committerabs <abs@pkgsrc.org>2012-07-13 21:12:07 +0000
commit608db4d95024a92a3eb23f2c21fac016c960f133 (patch)
treef910c1c4cd2e3cb7f29bc7b0e211c2e18a24efae /pkgtools/lintpkgsrc
parent86aaa572f624f66b8a6694bce9f57720e69ca076 (diff)
downloadpkgsrc-608db4d95024a92a3eb23f2c21fac016c960f133.tar.gz
Updated pkgtools/lintpkgsrc to 4.84
4.84: - Misparse Makefiles a little better. Check for '.' in the left hand of :S and :C operations, and also handle :U - Move files from pkglint/files to lintpkgrsc/files
Diffstat (limited to 'pkgtools/lintpkgsrc')
-rw-r--r--pkgtools/lintpkgsrc/Makefile8
-rw-r--r--pkgtools/lintpkgsrc/files/lintpkgsrc.0131
-rw-r--r--pkgtools/lintpkgsrc/files/lintpkgsrc.1194
-rwxr-xr-xpkgtools/lintpkgsrc/files/lintpkgsrc.pl1839
4 files changed, 2167 insertions, 5 deletions
diff --git a/pkgtools/lintpkgsrc/Makefile b/pkgtools/lintpkgsrc/Makefile
index f76e1057d09..38474fc9d0f 100644
--- a/pkgtools/lintpkgsrc/Makefile
+++ b/pkgtools/lintpkgsrc/Makefile
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.6 2012/05/08 23:11:48 sbd Exp $
-#
+# $NetBSD: Makefile,v 1.7 2012/07/13 21:12:07 abs Exp $
-DISTNAME= lintpkgsrc-4.83
+DISTNAME= lintpkgsrc-4.84
CATEGORIES= pkgtools
MASTER_SITES= # none
DISTFILES= # none
@@ -17,7 +16,6 @@ PKG_INSTALLATION_TYPES= overwrite pkgviews
USE_TOOLS+= perl:run
-FILESDIR= ${.CURDIR}/../../pkgtools/pkglint/files
WRKSRC= ${WRKDIR}
NO_BUILD= yes
USE_LANGUAGES= # none
@@ -35,7 +33,7 @@ SUBST_SED.lp+= -e s\|@PREFIX@\|${PREFIX}\|g
SUBST_SED.lp+= -e s\|@MAKE@\|${MAKE:Q}\|g
SUBST_SED.lp+= -e s\|@PERL@\|${PERL5:Q}\|g
-PKG_DESTDIR_SUPPORT= user-destdir
+PKG_DESTDIR_SUPPORT= user-destdir
.include "../../mk/bsd.prefs.mk"
diff --git a/pkgtools/lintpkgsrc/files/lintpkgsrc.0 b/pkgtools/lintpkgsrc/files/lintpkgsrc.0
new file mode 100644
index 00000000000..9ee45180428
--- /dev/null
+++ b/pkgtools/lintpkgsrc/files/lintpkgsrc.0
@@ -0,0 +1,131 @@
+LINTPKGSRC(1) NetBSD General Commands Manual LINTPKGSRC(1)
+
+NNAAMMEE
+ lliinnttppkkggssrrcc -- verifier for the pkgsrc tree
+
+SSYYNNOOPPSSIISS
+ lliinnttppkkggssrrcc [--BBDDLLOORRSSVVddhhiillmmoopprruu] [--EE _f_i_l_e] [--gg _p_k_g_s_r_c_m_a_p] [--II _f_i_l_e]
+ [--KK _P_A_C_K_A_G_E_S] [--MM _D_I_S_T_D_I_R] [--PP _P_K_G_S_R_C_D_I_R] [_m_a_k_e_f_i_l_e _._._.]
+
+DDEESSCCRRIIPPTTIIOONN
+ lliinnttppkkggssrrcc tries to verify the entire contents of the pkgsrc tree.
+ lliinnttppkkggssrrcc uses simple regular-expression matching for verifying files
+ that make up a pkgsrc directory. Note that it does NOT implement com-
+ plete Makefile parser.
+
+ OOppttiioonnss
+ --BB List packages marked as BROKEN.
+
+ --DD _p_a_t_h_s Parse Makefiles and output contents (useful for debugging).
+
+ --dd Extract the `DEPENDS' listing from each pkgsrc package
+ Makefile, then verify the version specified in each DEPEND
+ correctly matches a current pkgsrc package.
+
+ --EE _f_i_l_e Exports the internal database generated from the pkgsrc
+ Makefiles to a file for faster loading at a later time.
+ Note that some of the stored data is, in all likelihood,
+ dependent on the architecture and operating system of the
+ machine where it is generated. However, the database does
+ make the task of, eg, checking for out of date packages on
+ a set of similarly configured machines (in the absence of
+ extra copies of the pkgsrc tree) both quick and possible.
+
+ --gg _p_k_g_s_r_c_m_a_p Generate pkgsrcmap file containing an entry for each pack-
+ age of `pkgname pkgdir pkgver'.
+
+ --hh Basic help and usage.
+
+ --II _f_i_l_e Imports the data to construct the internal database for
+ checking the installed versions of packages, among other
+ things, against the current version found in pkgsrc. This
+ database provides most or all of the data required for run-
+ ning lliinnttppkkggssrrcc with any combination of --BB, --OO, --RR, --SS, --VV,
+ --dd, --gg, --ii, --pp, or --uu.
+
+ --ii Check the version of each installed package against the
+ current version in pkgsrc.
+
+ --KK _P_A_C_K_A_G_E_S Override binary packages base directory, normally set from
+ _/_e_t_c_/_m_k_._c_o_n_f or defaulted to _$_{_P_K_G_S_R_C_D_I_R_}_/_p_a_c_k_a_g_e_s.
+
+ Note: lintpkgsrc treats _P_A_C_K_A_G_E_S slightly differently to
+ the pkgsrc build system. --pp, --RR and --VV check for binary
+ packages in any depth of PACKAGES subdirs - this allows
+ packages for all architectures/OS versions to be checked
+ simultaneously with an _/_e_t_c_/_m_k_._c_o_n_f construct of the form:
+
+ .ifdef LINTPKGSRC
+ PACKAGES=${PKGSRCDIR}/packages
+ .else
+ PACKAGES=${PKGSRCDIR}/packages/${OS_VERSION}/${MACHINE_ARCH}
+ .endif
+
+ Which would be an alternative to --KK.
+
+ --LL List the filenames of Makefiles as they are parsed (for
+ debugging).
+
+ --MM _D_I_S_T_D_I_R Set directory for distfiles, defaults to
+ _$_{_P_K_G_S_R_C_D_I_R_}_/_d_i_s_t_f_i_l_e_s.
+
+ --mm For each current distfile, verify its checksum against the
+ appropriate package's distinfo file. Also report if any
+ package's distinfo file references the same distfile with a
+ different distinfo checksum.
+
+ --OO Report any binary packages in any subdirs of _P_A_C_K_A_G_E_S, for
+ whom the source Makefile lists _O_S_V_E_R_S_I_O_N___S_P_E_C_I_F_I_C. This is
+ intended to help those making binary packages available for
+ ftp.
+
+ --oo Report any old distfiles (not referenced by any package's
+ distinfo file).
+
+ --PP _P_K_G_S_R_C_D_I_R Set base of pkgsrc tree, normally set from _/_e_t_c_/_m_k_._c_o_n_f or
+ defaulted to _/_u_s_r_/_p_k_g_s_r_c.
+
+ --pp Report any old binary packages in any subdirs of _P_A_C_K_A_G_E_S.
+
+ --RR Report any binary packages in any subdirs of _P_A_C_K_A_G_E_S, for
+ whom the source Makefile lists _N_O___B_I_N___O_N___F_T_P or _R_E_S_T_R_I_C_T_E_D.
+ This is intended to help those making binary packages
+ available for ftp.
+
+ --rr Remove distfiles which are not referenced from any pack-
+ age's distinfo file, or have an incorrect checksum (usually
+ due to an interrupted download), and any old binary pack-
+ ages. To remove any given type, additionally specify the
+ --oo, --mm, or --pp flags respectively.
+
+ --SS List packages missing from category _S_U_B_D_I_R lists.
+
+ --uu As --ii plus for mismatched packages, display REQUIRED
+ details and run `@MAKE@ fetch-list | sh' to ensure all the
+ distfiles needed for the latest version are present.
+
+ Warning: The behaviour of this command may change in a
+ later version.
+
+ --VV List any prebuilt packages in any subdirs of _P_A_C_K_A_G_E_S with
+ known vulnerabilities, based on the data in
+ _$_{_P_K_G_S_R_C_D_I_R_}_/_d_i_s_t_f_i_l_e_s_/_p_k_g_-_v_u_l_n_e_r_a_b_i_l_i_t_i_e_s.
+
+AAUUTTHHOORRSS
+ David Brownlee <abs@netbsd.org>
+
+BBUUGGSS
+ The --RR, --VV, and --pp options default to using _P_A_C_K_A_G_E_S as the base direc-
+ tory from which to search for binary packages. If this includes OS or
+ architecture information then packages for other OS/architecture combina-
+ tions will be missed. In this case _P_A_C_K_A_G_E_S can be overridden with --KK or
+ set conditionally in _/_e_t_c_/_m_k_._c_o_n_f based on the value of _L_I_N_T_P_K_G_S_R_C.
+
+ The `Makefile parsing' algorithm used to obtain package versions and
+ DEPENDS information is geared towards speed rather than perfection,
+ though it has got somewhat better over time, it only parses the simplest
+ Makefile conditionals. (a == b, no && etc).
+
+ Potentially others, but non serious to date.
+
+pkgsrc November 8, 2005 pkgsrc
diff --git a/pkgtools/lintpkgsrc/files/lintpkgsrc.1 b/pkgtools/lintpkgsrc/files/lintpkgsrc.1
new file mode 100644
index 00000000000..eaae39ae7ee
--- /dev/null
+++ b/pkgtools/lintpkgsrc/files/lintpkgsrc.1
@@ -0,0 +1,194 @@
+.\" $NetBSD: lintpkgsrc.1,v 1.1 2012/07/13 21:12:07 abs Exp $
+.\"
+.\" Copyright (c) 1999 by David Brownlee (abs@netbsd.org)
+.\" Absolutely no warranty.
+.\"
+.Dd November 8, 2005
+.Dt LINTPKGSRC 1
+.Sh NAME
+.Nm lintpkgsrc
+.Nd verifier for the pkgsrc tree
+.Sh SYNOPSIS
+.Nm
+.Op Fl BDLORSVdhilmopru
+.Op Fl E Ar file
+.Op Fl g Ar pkgsrcmap
+.Op Fl I Ar file
+.Op Fl K Ar PACKAGES
+.Op Fl M Ar DISTDIR
+.Op Fl P Ar PKGSRCDIR
+.Op Pa makefile ...
+.Sh DESCRIPTION
+.Nm
+tries to verify the entire contents of the pkgsrc tree.
+.Nm
+uses simple regular-expression matching for verifying
+files that make up a pkgsrc directory.
+Note that it does NOT implement complete Makefile parser.
+.Ss Options
+.Bl -tag -width xxxxxxxxxxxx
+.It Fl B
+List packages marked as BROKEN.
+.It Fl D Ar paths
+Parse Makefiles and output contents (useful for debugging).
+.It Fl d
+Extract the
+.Ql DEPENDS
+listing from each pkgsrc package Makefile, then
+verify the version specified in each DEPEND correctly matches a current
+pkgsrc package.
+.It Fl E Ar file
+Exports the internal database generated from the pkgsrc Makefiles to a
+file for faster loading at a later time.
+Note that some of the stored data is, in all likelihood, dependent on
+the architecture and operating system of the machine where it is
+generated.
+However, the database does make the task of, eg, checking for out of
+date packages on a set of similarly configured machines (in the
+absence of extra copies of the pkgsrc tree) both quick and possible.
+.It Fl g Ar pkgsrcmap
+Generate pkgsrcmap file containing an entry for each package of
+.Ql pkgname pkgdir pkgver .
+.It Fl h
+Basic help and usage.
+.It Fl I Ar file
+Imports the data to construct the internal database for checking the
+installed versions of packages, among other things, against the
+current version found in pkgsrc.
+This database provides most or all of the data required for running
+.Nm
+with any combination of
+.Fl B ,
+.Fl O ,
+.Fl R ,
+.Fl S ,
+.Fl V ,
+.Fl d ,
+.Fl g ,
+.Fl i ,
+.Fl p ,
+or
+.Fl u .
+.It Fl i
+Check the version of each installed package against the current version in
+pkgsrc.
+.It Fl K Ar PACKAGES
+Override binary packages base directory, normally set from
+.Pa /etc/mk.conf
+or defaulted to
+.Pa ${PKGSRCDIR}/packages .
+.Pp
+Note: lintpkgsrc treats
+.Em PACKAGES
+slightly differently to the pkgsrc build system.
+.Fl p ,
+.Fl R
+and
+.Fl V
+check for
+binary packages in any depth of PACKAGES subdirs - this allows packages for all
+architectures/OS versions to be checked simultaneously with an
+.Pa /etc/mk.conf
+construct of the form:
+.Bd -literal
+\&.ifdef LINTPKGSRC
+PACKAGES=${PKGSRCDIR}/packages
+\&.else
+PACKAGES=${PKGSRCDIR}/packages/${OS_VERSION}/${MACHINE_ARCH}
+\&.endif
+.Ed
+.Pp
+Which would be an alternative to
+.Fl K .
+.It Fl L
+List the filenames of Makefiles as they are parsed (for debugging).
+.It Fl M Ar DISTDIR
+Set directory for distfiles, defaults to
+.Pa ${PKGSRCDIR}/distfiles .
+.It Fl m
+For each current distfile, verify its checksum against the appropriate
+package's distinfo file. Also report if any package's distinfo file references
+the same distfile with a different distinfo checksum.
+.It Fl O
+Report any binary packages in any subdirs of
+.Em PACKAGES ,
+for whom the source Makefile lists
+.Em OSVERSION_SPECIFIC .
+This is intended to help those making binary packages available for ftp.
+.It Fl o
+Report any old distfiles (not referenced by any package's distinfo file).
+.It Fl P Ar PKGSRCDIR
+Set base of pkgsrc tree, normally set from
+.Pa /etc/mk.conf
+or defaulted to
+.Pa /usr/pkgsrc .
+.It Fl p
+Report any old binary packages in any subdirs of
+.Em PACKAGES .
+.It Fl R
+Report any binary packages in any subdirs of
+.Em PACKAGES ,
+for whom the source Makefile lists
+.Em NO_BIN_ON_FTP
+or
+.Em RESTRICTED .
+This is intended to help those making binary packages available for ftp.
+.It Fl r
+Remove distfiles which are not referenced from any package's distinfo file,
+or have an incorrect checksum (usually due to an interrupted download),
+and any old binary packages. To remove any given type, additionally
+specify the
+.Fl o ,
+.Fl m ,
+or
+.Fl p
+flags respectively.
+.It Fl S
+List packages missing from category
+.Em SUBDIR
+lists.
+.It Fl u
+As
+.Fl i
+plus for mismatched packages, display REQUIRED details and run
+.Ql @MAKE@ fetch-list | sh
+to ensure all the distfiles needed for
+the latest version are present.
+.Pp
+Warning: The behaviour of this command may change in a later version.
+.It Fl V
+List any prebuilt packages in any subdirs of
+.Em PACKAGES
+with known vulnerabilities, based on the data in
+.Pa ${PKGSRCDIR}/distfiles/pkg-vulnerabilities .
+.El
+.Sh AUTHORS
+.An David Brownlee Aq abs@netbsd.org
+.Sh BUGS
+The
+.Fl R ,
+.Fl V ,
+and
+.Fl p
+options default to using
+.Em PACKAGES
+as the base directory from which to
+search for binary packages. If this includes OS or architecture information
+then packages for other OS/architecture combinations will be missed.
+In this case
+.Em PACKAGES
+can be overridden with
+.Fl K
+or set conditionally in
+.Pa /etc/mk.conf
+based on the value of
+.Em LINTPKGSRC .
+.Pp
+The
+.Ql Makefile parsing
+algorithm used to obtain package versions
+and DEPENDS information is geared towards speed rather than
+perfection, though it has got somewhat better over time, it only
+parses the simplest Makefile conditionals. (a == b, no \*[Am]\*[Am] etc).
+.Pp
+Potentially others, but non serious to date.
diff --git a/pkgtools/lintpkgsrc/files/lintpkgsrc.pl b/pkgtools/lintpkgsrc/files/lintpkgsrc.pl
new file mode 100755
index 00000000000..797e805bf9c
--- /dev/null
+++ b/pkgtools/lintpkgsrc/files/lintpkgsrc.pl
@@ -0,0 +1,1839 @@
+#! @PERL@
+
+# $NetBSD: lintpkgsrc.pl,v 1.1 2012/07/13 21:12:07 abs Exp $
+
+# Written by David Brownlee <abs@netbsd.org>.
+#
+# Caveats:
+# The 'Makefile parsing' algorithm used to obtain package versions and
+# DEPENDS information is geared towards speed rather than perfection,
+# though it has gotten somewhat better over time, it only parses the
+# simpler Makefile conditionals.
+#
+# TODO: Handle fun DEPENDS like avifile-devel with
+# {qt2-designer>=2.2.4,qt2-designer-kde>=2.3.1nb1}
+
+$^W = 1;
+use locale;
+use strict;
+use Getopt::Std;
+use File::Find;
+use File::Basename;
+use IPC::Open3;
+use Cwd 'realpath', 'getcwd';
+
+# Buildtime configuration
+my $conf_make = '@MAKE@';
+my $conf_pkgsrcdir = '@PKGSRCDIR@';
+my $conf_prefix = '@PREFIX@';
+
+my (
+ $pkglist, # list of Pkg packages
+ $pkg_installver, # installed version of pkg_install pseudo-pkg
+ $default_vars, # Set for Makefiles, inc PACKAGES & PKGSRCDIR
+ %opt, # Command line options
+ %vuln, # vulnerability data
+ @matched_prebuiltpackages, # List of obsolete prebuilt package paths
+ @prebuilt_pkgdirs, # Use to follow symlinks in prebuilt pkgdirs
+ %prebuilt_pkgdir_cache, # To avoid symlink loops in prebuilt_pkgdirs
+);
+
+$ENV{PATH} .=
+ ":/bin:/usr/bin:/sbin:/usr/sbin:${conf_prefix}/sbin:${conf_prefix}/bin";
+
+if (
+ !getopts( 'BDE:I:K:LM:OP:RSVdg:himopru', \%opt )
+ || $opt{h}
+ || !(
+ defined $opt{d}
+ || defined $opt{g}
+ || defined $opt{i}
+ || defined $opt{m}
+ || defined $opt{o}
+ || defined $opt{p}
+ || defined $opt{r}
+ || defined $opt{u}
+ || defined $opt{B}
+ || defined $opt{D}
+ || defined $opt{R}
+ || defined $opt{O}
+ || defined $opt{S}
+ || defined $opt{V}
+ || defined $opt{E}
+ )
+ )
+{
+
+ usage_and_exit();
+}
+$| = 1;
+
+# Horrible kludge to ensure we have a value for testing in conditionals, but
+# gets removed in the final evaluation
+my $magic_undefined = 'M_a_G_i_C_uNdEfInEd';
+
+get_default_makefile_vars(); # $default_vars
+
+if ( $opt{D} && @ARGV ) {
+ foreach my $file (@ARGV) {
+ if ( -d $file ) {
+ $file .= "/Makefile";
+ }
+ if ( !-f $file ) {
+ fail("No such file: $file");
+ }
+ my ( $pkgname, $vars ) = parse_makefile_pkgsrc($file);
+ $pkgname ||= 'uNDEFINEd';
+ print "$file -> $pkgname\n";
+ foreach my $varname ( sort keys %{$vars} ) {
+ print "\t$varname = $vars->{$varname}\n";
+ }
+
+ #if ($opt{d}) {
+ # pkgsrc_check_depends();
+ #}
+ }
+ exit;
+}
+
+sub main() {
+ my ( $pkgsrcdir, $pkgdistdir );
+
+ $pkgsrcdir = $default_vars->{PKGSRCDIR};
+ $pkgdistdir = $default_vars->{DISTDIR};
+
+ if ( $opt{r} && !$opt{o} && !$opt{m} && !$opt{p} ) {
+ $opt{o} = $opt{m} = $opt{p} = 1;
+ }
+ if ( $opt{o} || $opt{m} ) {
+ my (@baddist);
+
+ @baddist =
+ scan_pkgsrc_distfiles_vs_distinfo( $pkgsrcdir, $pkgdistdir, $opt{o},
+ $opt{m} );
+ if ( $opt{r} ) {
+ verbose("Unlinking 'bad' distfiles\n");
+ foreach my $distfile (@baddist) {
+ unlink("$pkgdistdir/$distfile");
+ }
+ }
+ }
+
+ # List BROKEN packages
+ if ( $opt{B} ) {
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ foreach my $pkgver ( $pkglist->pkgver ) {
+ $pkgver->var('BROKEN') || next;
+ print $pkgver->pkgname . ': ' . $pkgver->var('BROKEN') . "\n";
+ }
+ }
+
+ # List obsolete or NO_BIN_ON_FTP/RESTRICTED prebuilt packages
+ #
+ if ( $opt{p} || $opt{O} || $opt{R} || $opt{V} ) {
+ if ( $opt{V} ) {
+ my ($vuln) = "$pkgdistdir/pkg-vulnerabilities";
+
+ if ( !open( VULN, $vuln ) ) {
+ fail("Unable to open '$vuln': $!");
+ }
+ while (<VULN>) {
+ s/#.*//;
+ if (/([^*?[]+)(<|>|<=|>=)(\d\S+)/) {
+ my ( $pkg, $cmp, $ver ) = ( $1, $2, $3 );
+ push( @{ $vuln{$pkg} }, "$cmp $ver" );
+ }
+ }
+ close(VULN);
+ }
+
+ if ( $opt{p} || $opt{O} || $opt{R} || $opt{V} ) {
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ }
+ @prebuilt_pkgdirs = ( $default_vars->{PACKAGES} );
+ %prebuilt_pkgdir_cache = ();
+
+ while (@prebuilt_pkgdirs) {
+ find( \&check_prebuilt_packages, shift @prebuilt_pkgdirs );
+ }
+
+ if ( $opt{r} ) {
+ verbose("Unlinking listed prebuiltpackages\n");
+ foreach my $pkgfile (@matched_prebuiltpackages) {
+ unlink($pkgfile);
+ }
+ }
+ }
+
+ if ( $opt{S} ) {
+ my (%in_subdir);
+
+ foreach my $cat ( list_pkgsrc_categories($pkgsrcdir) ) {
+ my $vars = parse_makefile_vars("$pkgsrcdir/$cat/Makefile");
+
+ if ( !$vars->{SUBDIR} ) {
+ print "Warning - no SUBDIR for $cat\n";
+ next;
+ }
+ foreach my $pkgdir ( split( /\s+/, $vars->{SUBDIR} ) ) {
+ $in_subdir{"$cat/$pkgdir"} = 1;
+ }
+ }
+
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ foreach my $pkgver ( $pkglist->pkgver ) {
+ if ( !defined $in_subdir{ $pkgver->var('dir') } ) {
+ print $pkgver->var('dir') . ": Not in SUBDIR\n";
+ }
+ }
+ }
+
+ if ( $opt{g} ) {
+ my $tmpfile = "$opt{g}.tmp.$$";
+
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ if ( !open( TABLE, ">$tmpfile" ) ) {
+ fail("Unable to write '$tmpfile': $!");
+ }
+ foreach my $pkgver ( $pkglist->pkgver ) {
+ print TABLE $pkgver->pkg . "\t"
+ . $pkgver->var('dir') . "\t"
+ . $pkgver->ver . "\n";
+ }
+ if ( !close(TABLE) ) {
+ fail("Error while writing '$tmpfile': $!");
+ }
+ if ( !rename( $tmpfile, $opt{g} ) ) {
+ fail("Error in rename('$tmpfile','$opt{g}'): $!");
+ }
+ }
+
+ if ( $opt{d} ) {
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ pkgsrc_check_depends();
+ }
+
+ if ( $opt{i} || $opt{u} ) {
+ my ( @pkgs, @update );
+
+ @pkgs = list_installed_packages();
+ scan_pkgsrc_makefiles($pkgsrcdir);
+
+ foreach my $pkgname ( sort @pkgs ) {
+ if ( $_ = invalid_version($pkgname) ) {
+ print $_;
+
+ if ( $pkgname =~ /^([^*?[]+)-([\d*?[].*)/ ) {
+ foreach my $pkgver ( $pkglist->pkgver($1) ) {
+ $pkgver->var('dir') =~ /-current/ && next;
+ push( @update, $pkgver );
+ last;
+ }
+ }
+ }
+ }
+
+ if ( $opt{u} ) {
+ print "\nREQUIRED details for packages that could be updated:\n";
+
+ foreach my $pkgver (@update) {
+ print $pkgver->pkg . ':';
+ if ( open( PKGINFO, 'pkg_info -R ' . $pkgver->pkg . '|' ) ) {
+ my ($list);
+
+ while (<PKGINFO>) {
+ if (/Required by:/) {
+ $list = 1;
+ }
+ elsif ($list) {
+ chomp;
+ s/-\d.*//;
+ print " $_";
+ }
+ }
+ close(PKGINFO);
+ }
+ print "\n";
+ }
+
+ print
+ "\nRunning '${conf_make} fetch-list | sh' for each package:\n";
+ foreach my $pkgver (@update) {
+ my ($pkgdir);
+
+ $pkgdir = $pkgver->var('dir');
+ if ( !defined($pkgdir) ) {
+ fail(
+ 'Unable to determine ' . $pkgver->pkg . ' directory' );
+ }
+
+ print "$pkgsrcdir/$pkgdir\n";
+ safe_chdir("$pkgsrcdir/$pkgdir");
+ system("${conf_make} fetch-list | sh");
+ }
+ }
+ }
+
+ if ( $opt{E} ) {
+ scan_pkgsrc_makefiles($pkgsrcdir);
+ store_pkgsrc_makefiles( $opt{E} );
+ }
+}
+
+sub canonicalize_pkgname($) {
+ my ($pkgname) = @_;
+
+ $pkgname =~ s,^py\d+(?:pth|)-,py-,;
+ $pkgname =~ s,^ruby\d+-,ruby-,;
+ $pkgname =~ s,^php\d+-,php-,;
+ return $pkgname;
+}
+
+# Could speed up by building a cache of package names to paths, then processing
+# each package name once against the tests.
+sub check_prebuilt_packages() {
+
+ if ( $_ eq 'distfiles' || $_ eq 'pkgsrc' ) {
+
+ # Skip these subdirs if present
+ $File::Find::prune = 1;
+
+ }
+ elsif (/(.+)-(\d.*)\.t[bg]z$/) {
+ my ( $pkg, $ver ) = ( $1, $2 );
+
+ $pkg = canonicalize_pkgname($pkg);
+
+ if ( $opt{V} && $vuln{$pkg} ) {
+ foreach my $chk ( @{ $vuln{$pkg} } ) {
+ my ( $test, $matchver ) = split( ' ', $chk );
+
+ if ( deweycmp( $ver, $test, $matchver ) ) {
+ print "$File::Find::dir/$_\n";
+ push( @matched_prebuiltpackages, "$File::Find::dir/$_" );
+ last;
+ }
+ }
+ }
+
+ my ($pkgs);
+ if ( $pkgs = $pkglist->pkgs($pkg) ) {
+ my ($pkgver) = $pkgs->pkgver($ver);
+
+ if ( !defined $pkgver ) {
+ if ( $opt{p} ) {
+ print "$File::Find::dir/$_\n";
+ push( @matched_prebuiltpackages, "$File::Find::dir/$_" );
+ }
+
+ # Pick probably the last version
+ $pkgver = $pkgs->latestver;
+ }
+
+ if ( $opt{R} && $pkgver->var('RESTRICTED') ) {
+ print "$File::Find::dir/$_\n";
+ push( @matched_prebuiltpackages, "$File::Find::dir/$_" );
+ }
+
+ if ( $opt{O} && $pkgver->var('OSVERSION_SPECIFIC') ) {
+ print "$File::Find::dir/$_\n";
+ push( @matched_prebuiltpackages, "$File::Find::dir/$_" );
+ }
+ }
+
+ }
+ elsif ( -d $_ ) {
+ if ( $prebuilt_pkgdir_cache{"$File::Find::dir/$_"} ) {
+ $File::Find::prune = 1;
+ return;
+ }
+
+ $prebuilt_pkgdir_cache{"$File::Find::dir/$_"} = 1;
+ if ( -l $_ ) {
+ my ($dest) = readlink($_);
+
+ if ( substr( $dest, 0, 1 ) ne '/' ) {
+ $dest = "$File::Find::dir/$dest";
+ }
+ if ( !$prebuilt_pkgdir_cache{$dest} ) {
+ push( @prebuilt_pkgdirs, $dest );
+ }
+ }
+ }
+}
+
+# Dewey decimal verson number matching - or thereabouts
+# Also handles 'nb<N>' suffix (checked iff values otherwise identical)
+#
+sub deweycmp($$$) {
+ my ( $match, $test, $val ) = @_;
+ my ( $cmp, $match_nb, $val_nb );
+
+ $match_nb = $val_nb = 0;
+ if ( $match =~ /(.*)nb(.*)/ ) {
+
+ # Handle nb<N> suffix
+ $match = $1;
+ $match_nb = $2;
+ }
+
+ if ( $val =~ /(.*)nb(.*)/ ) {
+
+ # Handle nb<N> suffix
+ $val = $1;
+ $val_nb = $2;
+ }
+
+ $cmp = deweycmp_extract( $match, $val );
+
+ if ( !$cmp ) {
+
+ # Iff otherwise identical, check nb suffix
+ $cmp = deweycmp_extract( $match_nb, $val_nb );
+ }
+
+ eval "$cmp $test 0";
+}
+
+sub convert_to_standard_dewey(@) {
+ my ( $elem, $underscore, @temp );
+
+ # According to the current implementation in pkg_install/lib/str.c
+ # as of 2002/06/02, '_' before a number, '.', and 'pl' get treated as 0,
+ # while 'rc' and 'pre' get treated as -1; beta as '-2', alpha as '-3'.
+ # Other characters are converted to lower
+ # case and then to a number: a->1, b->2, c->3, etc. Numbers stay the same.
+ # 'nb' is a special case that's already been handled when we are here.
+ foreach $elem (@_) {
+ if ( $elem =~ /\d+/ ) {
+ push( @temp, $elem );
+
+ }
+ elsif ( $elem =~ /^pl$/ or $elem =~ /^\.$/ ) {
+ push( @temp, 0 );
+
+ }
+ elsif ( $elem =~ /^_$/ ) {
+ push( @temp, 0 );
+
+ }
+ elsif ( $elem =~ /^pre$/ ) {
+ push( @temp, -1 );
+
+ }
+ elsif ( $elem =~ /^rc$/ ) {
+ push( @temp, -1 );
+
+ }
+ elsif ( $elem =~ /^beta$/ ) {
+ push( @temp, -2 );
+
+ }
+ elsif ( $elem =~ /^alpha$/ ) {
+ push( @temp, -3 );
+
+ }
+ else {
+ push( @temp, 0 );
+ push( @temp, ord($elem) - ord("a") + 1 );
+ }
+ }
+ @temp;
+}
+
+sub deweycmp_extract($$) {
+ my ( $match, $val ) = @_;
+ my ( $cmp, @matchlist, @vallist, $i, $len );
+
+ @matchlist = convert_to_standard_dewey( split( /(\D+)/, lc($match) ) );
+ @vallist = convert_to_standard_dewey( split( /(\D+)/, lc($val) ) );
+ $cmp = 0;
+ $i = 0;
+ if ( $#matchlist > $#vallist ) {
+ $len = $#matchlist;
+ }
+ else {
+ $len = $#vallist;
+ }
+ while ( !$cmp && ( $i++ <= $len ) ) {
+ if ( !@matchlist ) {
+ push( @matchlist, 0 );
+ }
+ if ( !@vallist ) {
+ push( @vallist, 0 );
+ }
+ $cmp = ( shift @matchlist <=> shift @vallist );
+ }
+ $cmp;
+}
+
+sub fail(@) {
+
+ print STDERR @_, "\n";
+ exit(3);
+}
+
+sub get_default_makefile_vars() {
+
+ chomp( $pkg_installver = `pkg_info -V 2>/dev/null || echo 20010302` );
+
+ chomp( $_ = `uname -srm` );
+ (
+ $default_vars->{OPSYS},
+ $default_vars->{OS_VERSION},
+ $default_vars->{MACHINE}
+ ) = (split);
+ if ( !$default_vars->{MACHINE} ) {
+ die('Unable to extract machine from uname');
+ }
+
+ # Handle systems without uname -p (NetBSD pre 1.4)
+ chomp( $default_vars->{MACHINE_ARCH} = `uname -p 2>/dev/null` );
+
+ if ( !$default_vars->{MACHINE_ARCH}
+ && $default_vars->{OS_VERSION} eq 'NetBSD' )
+ {
+ chomp( $default_vars->{MACHINE_ARCH} = `sysctl -n hw.machine_arch` );
+ }
+
+ if ( !$default_vars->{MACHINE_ARCH} ) {
+ $default_vars->{MACHINE_ARCH} = $default_vars->{MACHINE};
+ }
+
+ $default_vars->{OBJECT_FMT} = 'x';
+ $default_vars->{LOWER_OPSYS} = lc( $default_vars->{OPSYS} );
+
+ if ( $opt{P} ) {
+ $default_vars->{PKGSRCDIR} = $opt{P};
+ }
+ else {
+ $default_vars->{PKGSRCDIR} = $conf_pkgsrcdir;
+ }
+
+ $default_vars->{DESTDIR} = '';
+ $default_vars->{LOCALBASE} = '/usr/pkg';
+ $default_vars->{X11BASE} = '/usr/X11R6';
+
+ my ($vars);
+ if ( -f '/etc/mk.conf' && ( $vars = parse_makefile_vars('/etc/mk.conf') ) )
+ {
+ foreach my $var ( keys %{$vars} ) {
+ $default_vars->{$var} = $vars->{$var};
+ }
+ }
+
+ if ( $opt{P} ) {
+ $default_vars->{PKGSRCDIR} = $opt{P};
+ }
+
+ if ( $opt{M} ) {
+ $default_vars->{DISTDIR} = $opt{M};
+ }
+ else {
+ $default_vars->{DISTDIR} ||= $default_vars->{PKGSRCDIR} . '/distfiles';
+ }
+
+ if ( $opt{K} ) {
+ $default_vars->{PACKAGES} = $opt{K};
+ }
+
+ # Extract some variables from bsd.pkg.mk
+ my ($mkvars);
+ $mkvars = parse_makefile_vars(
+ "$default_vars->{PKGSRCDIR}/mk/bsd.pkg.mk",
+ "$default_vars->{PKGSRCDIR}/mk/scripts"
+ );
+ foreach my $varname ( keys %{$mkvars} ) {
+ if ( $varname =~ /_REQD$/ || $varname eq 'EXTRACT_SUFX' ) {
+ $default_vars->{$varname} = $mkvars->{$varname};
+ }
+ }
+
+ $default_vars->{PACKAGES} ||= $default_vars->{PKGSRCDIR} . '/packages';
+}
+
+# Determine if a package version is current. If not, report correct version
+# if found
+#
+sub invalid_version($) {
+ my ($pkgmatch) = @_;
+ my ( $fail, $ok );
+ my ( @pkgmatches, @todo );
+
+ @todo = ($pkgmatch);
+
+ # We handle {} here, everything else in package_globmatch
+ while ( $pkgmatch = shift @todo ) {
+ if ( $pkgmatch =~ /(.*){([^{}]+)}(.*)/ ) {
+ foreach ( split( ',', $2 ) ) {
+ push( @todo, "$1$_$3" );
+ }
+ }
+ else {
+ push( @pkgmatches, $pkgmatch );
+ }
+ }
+
+ foreach $pkgmatch (@pkgmatches) {
+ my ( $pkg, $badver ) = package_globmatch($pkgmatch);
+
+ if ( defined($badver) ) {
+ my ($pkgs);
+
+ if ( $pkgs = $pkglist->pkgs($pkg) ) {
+ $fail .=
+ "Version mismatch: '$pkg' $badver vs "
+ . join( ',', $pkgs->versions ) . "\n";
+ }
+ else {
+ $fail .= "Unknown package: '$pkg' version $badver\n";
+ }
+ }
+ else {
+
+ # If we find one match, don't bitch about others
+ $ok = 1;
+ }
+ }
+ $ok && ( $fail = undef );
+ $fail;
+}
+
+# List (recursive) non directory contents of specified directory
+#
+sub listdir($$) {
+ my ( $base, $dir ) = @_;
+ my ($thisdir);
+ my ( @list, @thislist );
+
+ $thisdir = $base;
+ if ( defined($dir) ) {
+ $thisdir .= "/$dir";
+ $dir .= '/';
+ }
+ else {
+ $dir = '';
+ }
+
+ opendir( DIR, $thisdir ) || fail("Unable to opendir($thisdir): $!");
+ @thislist = grep( substr( $_, 0, 1 ) ne '.' && $_ ne 'CVS', readdir(DIR) );
+ closedir(DIR);
+ foreach my $entry (@thislist) {
+ if ( -d "$thisdir/$entry" ) {
+ push( @list, listdir( $base, "$dir$entry" ) );
+ }
+ else {
+ push( @list, "$dir$entry" );
+ }
+ }
+ @list;
+}
+
+# Use pkg_info to list installed packages
+#
+sub list_installed_packages() {
+ my (@pkgs);
+
+ open( PKG_INFO, 'pkg_info -e "*" |' ) || fail("Unable to run pkg_info: $!");
+ while ( defined( my $pkg = <PKG_INFO> ) ) {
+ chomp($pkg);
+ push( @pkgs, canonicalize_pkgname($pkg) );
+ }
+ close(PKG_INFO);
+
+ @pkgs;
+}
+
+# List top level pkgsrc categories
+#
+sub list_pkgsrc_categories($) {
+ my ($pkgsrcdir) = @_;
+ my (@categories);
+
+ opendir( BASE, $pkgsrcdir ) || die("Unable to opendir($pkgsrcdir): $!");
+ @categories =
+ grep( substr( $_, 0, 1 ) ne '.'
+ && $_ ne 'CVS'
+ && -f "$pkgsrcdir/$_/Makefile",
+ readdir(BASE) );
+ closedir(BASE);
+ @categories;
+}
+
+# For a given category, list potentially valid pkgdirs
+#
+sub list_pkgsrc_pkgdirs($$) {
+ my ( $pkgsrcdir, $cat ) = @_;
+ my (@pkgdirs);
+
+ if ( !opendir( CAT, "$pkgsrcdir/$cat" ) ) {
+ die("Unable to opendir($pkgsrcdir/cat): $!");
+ }
+ @pkgdirs =
+ sort grep( $_ ne 'Makefile'
+ && $_ ne 'pkg'
+ && $_ ne 'CVS'
+ && substr( $_, 0, 1 ) ne '.',
+ readdir(CAT) );
+ close(CAT);
+ @pkgdirs;
+}
+
+sub glob2regex($) {
+ my ($glob) = @_;
+ my ( @chars, $in_alt );
+ my ($regex);
+
+ @chars = split( //, $glob );
+ while ( defined( $_ = shift @chars ) ) {
+ if ( $_ eq '*' ) {
+ $regex .= '.*';
+ }
+ elsif ( $_ eq '?' ) {
+ $regex .= '.';
+ }
+ elsif ( $_ eq '+' ) {
+ $regex .= '.';
+ }
+ elsif ( $_ eq '\\+' ) {
+ $regex .= $_ . shift @chars;
+ }
+ elsif ( $_ eq '.' || $_ eq '|' ) {
+ $regex .= quotemeta;
+ }
+ elsif ( $_ eq '{' ) {
+ $regex .= '(';
+ ++$in_alt;
+ }
+ elsif ( $_ eq '}' ) {
+ if ( !$in_alt ) {
+
+ # Error
+ return undef;
+ }
+ $regex .= ')';
+ --$in_alt;
+ }
+ elsif ( $_ eq ',' && $in_alt ) {
+ $regex .= '|';
+ }
+ else {
+ $regex .= $_;
+ }
+ }
+
+ if ($in_alt) {
+
+ # Error
+ return undef;
+ }
+ if ( $regex eq $glob ) {
+ return ('');
+ }
+ if ( $opt{D} ) {
+ print "glob2regex: $glob -> $regex\n";
+ }
+ '^' . $regex . '$';
+}
+
+# Perform some (reasonable) subset of 'pkg_info -e' / glob(3)
+# Returns (sometimes best guess at) package name,
+# and either 'problem version' or undef if all OK
+#
+sub package_globmatch($) {
+ my ($pkgmatch) = @_;
+ my ( $matchpkgname, $matchver, $regex );
+
+ if ( $pkgmatch =~ /^([^*?[]+)(<|>|<=|>=|-)(\d[^*?[{]*)$/ ) {
+
+ # (package)(cmp)(dewey)
+ my ( $test, @pkgvers );
+
+ ( $matchpkgname, $test, $matchver ) = ( $1, $2, $3 );
+ if ( @pkgvers = $pkglist->pkgver($matchpkgname) ) {
+ foreach my $pkgver (@pkgvers) {
+ if ( $test eq '-' ) {
+ if ( $pkgver->ver eq $matchver ) {
+ $matchver = undef;
+ last;
+ }
+ }
+ else {
+ if ( deweycmp( $pkgver->ver, $test, $matchver ) ) {
+ $matchver = undef;
+ last;
+ }
+ }
+ }
+
+ if ( $matchver && $test ne '-' ) {
+ $matchver = "$test$matchver";
+ }
+ }
+
+ }
+ elsif ( $pkgmatch =~ /^([^[]+)-([\d*?{[].*)$/ ) {
+
+ # (package)-(globver)
+ my (@pkgnames);
+
+ ( $matchpkgname, $matchver ) = ( $1, $2 );
+
+ if ( defined $pkglist->pkgs($matchpkgname) ) {
+ push( @pkgnames, $matchpkgname );
+
+ }
+ elsif ( $regex = glob2regex($matchpkgname) ) {
+ foreach my $pkg ( $pkglist->pkgs ) {
+ ( $pkg->pkg() =~ /$regex/ ) && push( @pkgnames, $pkg->pkg() );
+ }
+ }
+
+ # Try to convert $matchver into regex version
+ #
+ $regex = glob2regex($matchver);
+
+ foreach my $pkg (@pkgnames) {
+ if ( defined $pkglist->pkgver( $pkg, $matchver ) ) {
+ return ($matchver);
+ }
+
+ if ($regex) {
+ foreach my $ver ( $pkglist->pkgs($pkg)->versions ) {
+ if ( $ver =~ /$regex/ ) {
+ $matchver = undef;
+ last;
+ }
+ }
+ }
+
+ $matchver || last;
+ }
+
+ # last ditch attempt to handle the whole DEPENDS as a glob
+ #
+ if ( $matchver && ( $regex = glob2regex($pkgmatch) ) ) {
+
+ # (large-glob)
+ foreach my $pkgver ( $pkglist->pkgver ) {
+ if ( $pkgver->pkgname =~ /$regex/ ) {
+ $matchver = undef;
+ last;
+ }
+ }
+ }
+
+ }
+ else {
+ ( $matchpkgname, $matchver ) = ( $pkgmatch, 'missing' );
+ }
+
+ ( $matchpkgname, $matchver );
+}
+
+# Parse a pkgsrc package makefile and return the pkgname and set variables
+#
+sub parse_makefile_pkgsrc($) {
+ my ($file) = @_;
+ my ( $pkgname, $vars );
+
+ $vars = parse_makefile_vars($file);
+
+ if ( !$vars ) {
+
+ # Missing Makefile
+ return undef;
+ }
+
+ if ( defined $vars->{PKGNAME} ) {
+ $pkgname = $vars->{PKGNAME};
+
+ }
+ elsif ( defined $vars->{DISTNAME} ) {
+ $pkgname = $vars->{DISTNAME};
+ }
+
+ if ( defined $vars->{PKGNAME} ) {
+ debug("$file: PKGNAME=$vars->{PKGNAME}\n");
+ }
+ if ( defined $vars->{DISTNAME} ) {
+ debug("$file: DISTNAME=$vars->{DISTNAME}\n");
+ }
+
+ if ( !defined $pkgname || $pkgname !~ /(.*)-(\d.*)/ ) {
+
+ # invoke make here as a last resort
+ my ($pkgsrcdir) = ( $file =~ m:(/.*)/: );
+ my $pid = open3( \*WTR, \*RDR, \*ERR,
+ "cd $pkgsrcdir ; ${conf_make} show-vars VARNAMES=PKGNAME" );
+ if ( !$pid ) {
+ warn "$file: Unable to run make: $!";
+ }
+ else {
+ close(WTR);
+ my @errors = <ERR>;
+ close(ERR);
+ my ($makepkgname) = <RDR>;
+ close(RDR);
+ wait;
+ chomp @errors;
+ if (@errors) { warn "\n$file: @errors\n"; }
+
+ if ( $makepkgname =~ /(.*)-(\d.*)/ ) {
+ $pkgname = $makepkgname;
+ }
+ }
+ }
+
+ if ( defined $pkgname ) {
+ if ( $pkgname =~ /^pkg_install-(\d+)$/ && $1 < $pkg_installver ) {
+ $pkgname = "pkg_install-$pkg_installver";
+ }
+
+ $pkgname = canonicalize_pkgname($pkgname);
+
+ if ( defined $vars->{PKGREVISION}
+ and not $vars->{PKGREVISION} =~ /^\s*$/ )
+ {
+ if ( $vars->{PKGREVISION} =~ /\D/ ) {
+ print
+ "\nBogus: PKGREVISION $vars->{PKGREVISION} (from $file)\n";
+
+ }
+ elsif ( $vars->{PKGREVISION} ) {
+ $pkgname .= "nb";
+ $pkgname .= $vars->{PKGREVISION};
+ }
+ }
+
+ if ( $pkgname =~ /\$/ ) {
+ print "\nBogus: $pkgname (from $file)\n";
+
+ }
+ elsif ( $pkgname =~ /(.*)-(\d.*)/ ) {
+ if ($pkglist) {
+ my ($pkgver) = $pkglist->add( $1, $2 );
+
+ debug("add $1 $2\n");
+
+ foreach
+ my $var (qw(DEPENDS RESTRICTED OSVERSION_SPECIFIC BROKEN))
+ {
+ $pkgver->var( $var, $vars->{$var} );
+ }
+
+ if ( defined $vars->{NO_BIN_ON_FTP} ) {
+ $pkgver->var( 'RESTRICTED', 'NO_BIN_ON_FTP' );
+ }
+
+ if ( $file =~ m:([^/]+/[^/]+)/Makefile$: ) {
+ $pkgver->var( 'dir', $1 );
+ }
+ else {
+ $pkgver->var( 'dir', 'unknown' );
+ }
+ }
+ }
+ else {
+ print "Cannot extract $pkgname version ($file)\n";
+ }
+
+ return ( $pkgname, $vars );
+
+ }
+ else {
+ return (undef);
+ }
+}
+
+# Extract variable assignments from Makefile
+# Much unpalatable magic to avoid having to use make (all for speed)
+#
+sub parse_makefile_vars($$) {
+ my ( $file, $cwd ) = @_;
+ my (
+ $pkgname, %vars, $plus, $value, @data,
+ %incfiles, # Cache of previously included fils
+ %incdirs, # Directories in which to check for includes
+ @if_false
+ ); # 0:true 1:false 2:nested-false&nomore-elsif
+
+ if ( !open( FILE, $file ) ) {
+ return (undef);
+ }
+ @data = map { chomp; $_ } <FILE>;
+ close(FILE);
+
+ $incdirs{"."} = 1;
+ $incdirs{ dirname($file) } = 1;
+
+ # Some Makefiles depend on these being set
+ if ( $file eq '/etc/mk.conf' ) {
+ $vars{LINTPKGSRC} = 'YES';
+ }
+ else {
+ %vars = %{$default_vars};
+ }
+ $vars{BSD_PKG_MK} = 'YES';
+
+ if ($cwd) {
+ $vars{'.CURDIR'} = $cwd;
+
+ }
+ elsif ( $file =~ m#(.*)/# ) {
+ $vars{'.CURDIR'} = $1;
+
+ }
+ else {
+ $vars{'.CURDIR'} = getcwd;
+ }
+
+ $incdirs{ $vars{'.CURDIR'} } = 1;
+ if ( $opt{L} ) {
+ print "$file\n";
+ }
+
+ while ( defined( $_ = shift(@data) ) ) {
+ s/\s*[^\\]#.*//;
+
+ # Continuation lines
+ #
+ while ( substr( $_, -1 ) eq "\\" ) {
+ substr( $_, -2 ) = shift @data;
+ }
+
+ # Conditionals
+ #
+ if (m#^\.\s*if(|def|ndef)\s+(.*)#) {
+ my ( $type, $false );
+
+ $type = $1;
+ if ( $if_false[$#if_false] ) {
+ push( @if_false, 2 );
+
+ }
+ elsif ( $type eq '' ) {
+
+ # Straight if
+ push( @if_false, parse_eval_make_false( $2, \%vars ) );
+
+ }
+ else {
+ $false = !defined( $vars{ parse_expand_vars( $2, \%vars ) } );
+ if ( $type eq 'ndef' ) {
+ $false = !$false;
+ }
+ push( @if_false, $false ? 1 : 0 );
+ }
+ debug("$file: .if$type (! @if_false)\n");
+ next;
+ }
+
+ if ( m#^\.\s*elif\s+(.*)# && @if_false ) {
+ if ( $if_false[$#if_false] == 0 ) {
+ $if_false[$#if_false] = 2;
+ }
+ elsif ( $if_false[$#if_false] == 1
+ && !parse_eval_make_false( $1, \%vars ) )
+ {
+ $if_false[$#if_false] = 0;
+ }
+ debug("$file: .elif (! @if_false)\n");
+ next;
+ }
+
+ if ( m#^\.\s*else\b# && @if_false ) {
+ $if_false[$#if_false] = $if_false[$#if_false] == 1 ? 0 : 1;
+ debug("$file: .else (! @if_false)\n");
+ next;
+ }
+
+ if (m#^\.\s*endif\b#) {
+ pop(@if_false);
+ debug("$file: .endif (! @if_false)\n");
+ next;
+ }
+
+ $if_false[$#if_false] && next;
+
+ # Included files (just unshift onto @data)
+ #
+ if (m#^\.\s*include\s+"([^"]+)"#) {
+ my ($incfile) = parse_expand_vars( $1, \%vars );
+
+ # At this point just skip any includes which we were not able to
+ # fully expand
+ if ( $incfile =~ m#/mk/bsd#
+ || $incfile =~ /$magic_undefined/
+ || ( !$opt{d} && $incfile =~ m#/(buildlink[^/]*\.mk)# ) )
+ {
+ debug("$file: .include \"$incfile\" skipped\n");
+ }
+ else {
+ debug("$file: .include \"$incfile\"\n");
+
+ # Expand any simple vars in $incfile
+ #
+
+ if ( substr( $incfile, 0, 1 ) ne '/' ) {
+ foreach my $dir ( keys %incdirs ) {
+ if ( -f "$dir/$incfile" ) {
+ $incfile = "$dir/$incfile";
+ last;
+ }
+ }
+ }
+
+ # perl 5.6.1 realpath() cannot handle files, only directories
+ # If the last component is a symlink this will give a false
+ # negative, but that is not a problem as the duplicate check
+ # is for performance
+ $incfile =~ m#^(.+)(/[^/]+)$#;
+
+ if ( !-f $incfile ) {
+ if ( !$opt{L} ) {
+ verbose("\n");
+ }
+
+ verbose("$file: Cannot locate $incfile in "
+ . join( " ", sort keys %incdirs )
+ . "\n" );
+
+ }
+ else {
+ $incfile = realpath($1) . $2;
+
+ if ( !$incfiles{$incfile} ) {
+ if ( $opt{L} ) {
+ print "inc $incfile\n";
+ }
+ $incfiles{$incfile} = 1;
+
+ if ( !open( FILE, $incfile ) ) {
+ verbose(
+ "Cannot open '$incfile' (from $file): $_ $!\n");
+ }
+ else {
+ my $NEWCURDIR = $incfile;
+ $NEWCURDIR =~ s#/[^/]*$##;
+ $incdirs{$NEWCURDIR} = 1;
+ unshift( @data, ".CURDIR=$vars{'.CURDIR'}" );
+ unshift( @data, map { chomp; $_ } <FILE> );
+ unshift( @data, ".CURDIR=$NEWCURDIR" );
+ close(FILE);
+ }
+ }
+ }
+ }
+ next;
+ }
+
+ if (/^ *([-\w\.]+)\s*([:+?]?)=\s*(.*)/) {
+ my ($key);
+
+ $key = $1;
+ $plus = $2;
+ $value = $3;
+
+ if ( $plus eq ':' ) {
+ $vars{$key} = parse_expand_vars( $value, \%vars );
+ }
+ elsif ( $plus eq '+' && defined $vars{$key} ) {
+ $vars{$key} .= " $value";
+ }
+ elsif ( $plus ne '?' || !defined $vars{$key} ) {
+ $vars{$key} = $value;
+ }
+ debug("assignment: $key$plus=[$value] ($vars{$key})\n");
+
+ # Give python a little hand (XXX - do we wanna consider actually
+ # implementing make .for loops, etc?
+ #
+ if ( $key eq "PYTHON_VERSIONS_ACCEPTED" ) {
+ my ($pv);
+
+ foreach $pv ( split( /\s+/, $vars{PYTHON_VERSIONS_ACCEPTED} ) )
+ {
+ $vars{"_PYTHON_VERSION_FIRSTACCEPTED"} ||= $pv;
+ $vars{"_PYTHON_VERSION_${pv}_OK"} = "yes";
+ }
+ }
+ }
+ }
+
+ debug("$file: expand\n");
+
+ # Handle variable substitutions FRED = a-${JIM:S/-/-b-/}
+ #
+ my ($loop);
+
+ for ( $loop = 1 ; $loop ; ) {
+ $loop = 0;
+ foreach my $key ( keys %vars ) {
+ if ( index( $vars{$key}, '$' ) == -1 ) {
+ next;
+ }
+
+ $_ = parse_expand_vars( $vars{$key}, \%vars );
+ if ( $_ ne $vars{$key} ) {
+ $vars{$key} = $_;
+ $loop = 1;
+
+ }
+ elsif ( $vars{$key} =~
+ m#\${([\w.]+):([CS]([^{}])[^{}\3]+\3[^{}\3]*\3[g1]*(|:[^{}]+)|U[^{}]+)}# )
+ {
+ my ( $left, $subvar, $right ) = ( $`, $1, $' );
+ my (@patterns) = split( ':', $2 );
+ my ($result);
+
+ $result = $vars{$subvar};
+ $result ||= '';
+
+ # If $vars{$subvar} contains a $ skip it on this pass.
+ # Hopefully it will get substituted and we can catch it
+ # next time around.
+ if ( index( $result, '${' ) != -1 ) {
+ next;
+ }
+
+ debug(
+ "$file: substitutelist $key ($result) $subvar (@patterns)\n"
+ );
+ foreach (@patterns) {
+ if (m#(U)(.*)#) {
+ $result ||= $2;
+ } elsif (m#([CS])(.)([^/]+)\2([^/]*)\2([1g]*)#) {
+
+ my ( $how, $from, $to, $global ) = ( $1, $3, $4, $5 );
+
+ debug(
+"$file: substituteglob $subvar, $how, $from, $to, $global\n"
+ );
+ if ( $how eq 'S' ) {
+
+ # Limited substitution - keep ^ and $
+ $from =~ s/([?.{}\]\[*+])/\\$1/g;
+ }
+ $to =~ s/\\(\d)/\$$1/g; # Change \1 etc to $1
+ $to =~ s/\&/\$&/g; # Change & to $1
+
+ my ($notfirst);
+ if ( $global =~ s/1// ) {
+ ( $from, $notfirst ) = split( '\s', $from, 2 );
+ }
+
+ debug(
+ "$file: substituteperl $subvar, $how, $from, $to\n"
+ );
+ eval "\$result =~ s/$from/$to/$global";
+ if ( defined $notfirst ) {
+ $result .= " $notfirst";
+ }
+ }
+ else {
+ next;
+ }
+ }
+
+ $vars{$key} = $left . $result . $right;
+ $loop = 1;
+ }
+ }
+ }
+
+ foreach my $key ( keys %vars ) {
+ $vars{$key} =~ s/$magic_undefined//;
+ }
+ \%vars;
+}
+
+sub parse_expand_vars($$) {
+ my ( $line, $vars ) = @_;
+
+ while ( $line =~ /\$\{([-\w.]+)\}/ ) {
+ if ( defined( ${$vars}{$1} ) ) {
+ $line = $` . ${$vars}{$1} . $';
+ }
+ else {
+ $line = $` . $magic_undefined . $';
+ }
+ }
+ $line;
+}
+
+sub parse_expand_vars_dumb($$) {
+ my ( $line, $vars ) = @_;
+
+ while ( $line =~ /\$\{([-\w.]+)\}/ ) {
+ if ( defined( ${$vars}{$1} ) ) {
+ $line = $` . ${$vars}{$1} . $';
+ }
+ else {
+ $line = $` . $magic_undefined . $';
+ }
+ }
+ $line;
+}
+
+sub parse_eval_make_false($$) {
+ my ( $line, $vars ) = @_;
+ my ( $false, $test );
+
+ $false = 0;
+ $test = parse_expand_vars_dumb( $line, $vars );
+
+ # XXX This is _so_ wrong - need to parse this correctly
+ $test =~ s/""/\r/g;
+ $test =~ s/"//g; # "
+ $test =~ s/\r/""/g;
+
+ debug("conditional: $test\n");
+
+ # XXX Could do something with target
+ while ( $test =~ /(target|empty|make|defined|exists)\s*\(([^()]+)\)/ ) {
+ my $testname = $1;
+ my $varname = $2;
+ my $var;
+
+ # Implement (some of) make's :M modifier
+ if ( $varname =~ /^([^:]+):M(.+)$/ ) {
+ $varname = $1;
+ my $match = $2;
+
+ $var = $${vars}{$varname};
+ $var = parse_expand_vars( $var, $vars ) if defined $var;
+
+ $match =~ s/([.+])/\\$1/g;
+ $match =~ s/\*/.*/g;
+ $match =~ s/\?/./g;
+ $match = '^' . $match . '$';
+ $var = ( $var =~ /$match/ ) if defined $var;
+ }
+ else {
+ $var = $${vars}{$varname};
+ $var = parse_expand_vars( $var, $vars ) if defined $var;
+ }
+
+ if ( defined $var && $var eq $magic_undefined ) {
+ $var = undef;
+ }
+
+ if ( $testname eq 'exists' ) {
+ $_ = ( -e $varname ) ? 1 : 0;
+
+ }
+ elsif ( $testname eq 'defined' ) {
+ $_ = defined($var) ? 1 : 0;
+
+ }
+ elsif ( $testname eq 'empty' ) {
+ $_ = ( ( not defined($var) or ( length($var) == 0 ) ) ? 1 : 0 );
+
+ }
+ else {
+ $_ = 0;
+ }
+
+ $test =~ s/$testname\s*\([^()]+\)/$_/;
+ debug("conditional: update to $test\n");
+ }
+
+ while ( $test =~ /([^\s()\|\&]+)\s+(!=|==)\s+([^\s()]+)/ ) {
+ if ( $2 eq '==' ) {
+ $_ = ( $1 eq $3 ) ? 1 : 0;
+ }
+ else {
+ $_ = ( $1 ne $3 ) ? 1 : 0;
+ }
+ $test =~ s/[^\s()\|\&]+\s+(!=|==)\s+[^\s()]+/$_/;
+ }
+
+ if ( $test !~ /[^<>\d()\s&|.!]/ ) {
+ $false = eval "($test)?0:1";
+ if ( !defined $false ) {
+ fail("Eval failed $line - $test");
+ }
+ debug( "conditional: evaluated to " . ( $false ? 0 : 1 ) . "\n" );
+
+ }
+ else {
+ $false = 0;
+ debug("conditional: defaulting to 0\n");
+ }
+ $false;
+}
+
+# chdir() || fail()
+#
+sub safe_chdir($) {
+ my ($dir) = @_;
+
+ debug("chdir: $dir");
+ if ( !chdir($dir) ) {
+ fail("Unable to chdir($dir): $!");
+ }
+}
+
+# Generate pkgname->category/pkg mapping, optionally check DEPENDS
+#
+sub scan_pkgsrc_makefiles($$) {
+ my ( $pkgsrcdir, $check_depends ) = @_;
+ my (@categories);
+
+ if ($pkglist) {
+
+ # Already done
+ return;
+ }
+
+ if ( $opt{I} ) {
+ load_pkgsrc_makefiles( $opt{I} );
+ return;
+ }
+
+ $pkglist = new PkgList;
+ @categories = list_pkgsrc_categories($pkgsrcdir);
+ verbose('Scan Makefiles: ');
+
+ if ( !$opt{L} ) {
+ verbose( '_' x @categories . "\b" x @categories );
+ }
+ else {
+ verbose("\n");
+ }
+
+ foreach my $cat ( sort @categories ) {
+ foreach my $pkgdir ( list_pkgsrc_pkgdirs( $pkgsrcdir, $cat ) ) {
+ my ( $pkg, $vars ) =
+ parse_makefile_pkgsrc("$pkgsrcdir/$cat/$pkgdir/Makefile");
+ }
+
+ if ( !$opt{L} ) {
+ verbose('.');
+ }
+ }
+
+ if ( !$opt{L} ) {
+ my ($len);
+
+ $_ = $pkglist->numpkgver() . ' packages';
+ $len = @categories - length($_);
+ verbose( "\b" x @categories, $_, ' ' x $len, "\b" x $len, "\n" );
+ }
+}
+
+# Cross reference all depends
+#
+sub pkgsrc_check_depends() {
+
+ foreach my $pkgver ( $pkglist->pkgver ) {
+ my ( $err, $msg );
+
+ defined $pkgver->var('DEPENDS') || next;
+ foreach my $depend ( split( " ", $pkgver->var('DEPENDS') ) ) {
+
+ $depend =~ s/:.*// || next;
+
+ $depend = canonicalize_pkgname($depend);
+ if ( ( $msg = invalid_version($depend) ) ) {
+ if ( !defined($err) ) {
+ print $pkgver->pkgname . " DEPENDS errors:\n";
+ }
+ $err = 1;
+ $msg =~ s/(\n)(.)/$1\t$2/g;
+ print "\t$msg";
+ }
+ }
+ }
+}
+
+# Extract all distinfo entries, then verify contents of distfiles
+#
+sub scan_pkgsrc_distfiles_vs_distinfo($$$$) {
+ my ( $pkgsrcdir, $pkgdistdir, $check_unref, $check_distinfo ) = @_;
+ my (@categories);
+ my ( %distfiles, %sumfiles, @distwarn, $numpkg );
+ my (%bad_distfiles);
+
+ @categories = list_pkgsrc_categories($pkgsrcdir);
+
+ verbose( 'Scan distinfo: ' . '_' x @categories . "\b" x @categories );
+ $numpkg = 0;
+ foreach my $cat ( sort @categories ) {
+ foreach my $pkgdir ( list_pkgsrc_pkgdirs( $pkgsrcdir, $cat ) ) {
+ if ( open( DISTINFO, "$pkgsrcdir/$cat/$pkgdir/distinfo" ) ) {
+ ++$numpkg;
+ while (<DISTINFO>) {
+ if (m/^(\w+) ?\(([^\)]+)\) = (\S+)/) {
+ my ( $dn, $ds, $dt );
+ $dt = $1;
+ $dn = $2;
+ $ds = $3;
+ if ( $dn =~ /^patch-[\w.\-]+$/ ) {
+ next;
+ }
+
+ # Strip leading ./ which sometimes gets added
+ # because of DISTSUBDIR=.
+ $dn =~ s/^(\.\/)*//;
+ if ( !defined $distfiles{$dn} ) {
+ $distfiles{$dn}{sumtype} = $dt;
+ $distfiles{$dn}{sum} = $ds;
+ $distfiles{$dn}{path} = "$cat/$pkgdir";
+
+ }
+ elsif ($distfiles{$dn}{sumtype} eq $dt
+ && $distfiles{$dn}{sum} ne $ds )
+ {
+ push( @distwarn,
+ "checksum mismatch between '$dt' for '$dn' "
+ . "in $cat/$pkgdir and $distfiles{$dn}{path}\n"
+ );
+ }
+ }
+ }
+ close(DISTINFO);
+ }
+ }
+ verbose('.');
+ }
+ verbose(" ($numpkg packages)\n");
+
+ # Do not mark the vulnerabilities file as unknown
+ $distfiles{'pkg-vulnerabilities'} = {
+ path => 'pkg-vulnerabilities',
+ sum => 'IGNORE'
+ };
+
+ foreach my $file ( listdir( "$pkgdistdir", undef ) ) {
+ my ($dist);
+
+ if ( !defined( $dist = $distfiles{$file} ) ) {
+ $bad_distfiles{$file} = 1;
+
+ }
+ else {
+ if ( $dist->{sum} ne 'IGNORE' ) {
+ push( @{ $sumfiles{ $dist->{sumtype} } }, $file );
+ }
+ }
+ }
+
+ if ( $check_unref && %bad_distfiles ) {
+ verbose( scalar( keys %bad_distfiles ),
+ " unreferenced file(s) in '$pkgdistdir':\n" );
+ print join( "\n", sort keys %bad_distfiles ), "\n";
+ }
+
+ if ($check_distinfo) {
+ if (@distwarn) {
+ verbose(@distwarn);
+ }
+
+ verbose("checksum mismatches\n");
+ safe_chdir($pkgdistdir);
+ foreach my $sum ( keys %sumfiles ) {
+ if ( $sum eq 'Size' ) {
+ foreach my $file ( @{ $sumfiles{$sum} } ) {
+ if ( !-f $file || -S $file != $distfiles{$file}{sum} ) {
+ print $file, " (Size)\n";
+ $bad_distfiles{$file} = 1;
+ }
+ }
+ next;
+ }
+
+ open( DIGEST, "digest $sum @{$sumfiles{$sum}}|" )
+ || fail("Run digest: $!");
+ while (<DIGEST>) {
+ if (m/^$sum ?\(([^\)]+)\) = (\S+)/) {
+ if ( $distfiles{$1}{sum} ne $2 ) {
+ print $1, " ($sum)\n";
+ $bad_distfiles{$1} = 1;
+ }
+ }
+ }
+ close(DIGEST);
+ }
+ safe_chdir('/'); # Do not want to stay in $pkgdistdir
+ }
+ ( sort keys %bad_distfiles );
+}
+
+sub load_pkgsrc_makefiles() {
+
+ open( STORE, "<$_[0]" ) || die("Cannot read pkgsrc store from $_[0]: $!\n");
+ my ($pkgver);
+ our ( $pkgcnt, $pkgnum, $subpkgcnt, $subpkgnum );
+ $pkglist = new PkgList;
+ while (<STORE>) {
+ eval $_;
+ }
+ close(STORE);
+}
+
+sub store_pkgsrc_makefiles() {
+ open( STORE, ">$_[0]" ) || die("Cannot save pkgsrc store to $_[0]: $!\n");
+ my $was = select(STORE);
+ print(
+ 'sub __pkgcount { $subpkgnum += $_[0]; ',
+ 'verbose("\rReading pkgsrc database: ',
+ '$pkgnum / $pkgcnt ($subpkgnum / $subpkgcnt) pkgs"); }',
+ "\n"
+ );
+ $pkglist->store;
+ print("verbose(\"...done\\n\");\n");
+ select($was);
+ close(STORE);
+}
+
+# Remember to update manual page when modifying option list
+#
+sub usage_and_exit() {
+ print "Usage: lintpkgsrc [opts] [makefiles]
+opts:
+ -h : This help. [see lintpkgsrc(1) for more information]
+
+Installed package options: Distfile options:
+ -i : Check version against pkgsrc -m : List distinfo mismatches
+ -u : As -i + fetch dist (may change) -o : List obsolete (no distinfo)
+
+Prebuilt package options: Makefile options:
+ -p : List old/obsolete -B : List packages marked as 'BROKEN'
+ -O : List OSVERSION_SPECIFIC -d : Check 'DEPENDS' up to date
+ -R : List NO_BIN_ON_FTP/RESTRICTED -S : List packages not in 'SUBDIRS'
+ -V : List known vulnerabilities
+
+Misc:
+ -E file : Export the internal pkgsrc database to file
+ -I file : Import the internal pkgsrc database to file (for use with -i)
+ -g file : Generate 'pkgname pkgdir pkgver' map in file
+ -r : Remove bad files (Without -m -o -p or -V implies all, can use -R)
+
+Modifiers:
+ -K path : Set PACKAGES basedir (default PKGSRCDIR/packages)
+ -M path : Set DISTDIR (default PKGSRCDIR/distfiles)
+ -P path : Set PKGSRCDIR (default $conf_pkgsrcdir)
+ -D : Debug makefile and glob parsing
+ -L : List each Makefile when scanned
+";
+ exit;
+}
+
+sub verbose(@) {
+
+ if ( -t STDERR ) {
+ print STDERR @_;
+ }
+}
+
+sub debug(@) {
+
+ ( $opt{D} ) && print STDERR 'DEBUG: ', @_;
+}
+
+# PkgList is the master list of all packages in pkgsrc.
+#
+package PkgList;
+
+sub add($@) {
+ my $self = shift;
+
+ if ( !$self->pkgs( $_[0] ) ) {
+ $self->{_pkgs}{ $_[0] } = new Pkgs $_[0];
+ }
+ $self->pkgs( $_[0] )->add(@_);
+}
+
+sub new($) {
+ my $class = shift;
+ my $self = {};
+ bless $self, $class;
+ return $self;
+}
+
+sub numpkgver($) {
+ my $self = shift;
+ scalar( $self->pkgver );
+}
+
+sub pkgver($@) {
+ my $self = shift;
+
+ if ( @_ == 0 ) {
+ my (@list);
+ foreach my $pkg ( $self->pkgs ) {
+ push( @list, $pkg->pkgver );
+ }
+ return (@list);
+ }
+
+ if ( defined $self->{_pkgs}{ $_[0] } ) {
+ return ( @_ > 1 )
+ ? $self->{_pkgs}{ $_[0] }->pkgver( $_[1] )
+ : $self->{_pkgs}{ $_[0] }->pkgver();
+ }
+ return;
+}
+
+sub pkgs($@) {
+ my $self = shift;
+
+ if (@_) {
+ return $self->{_pkgs}{ $_[0] };
+ }
+ else {
+ return ( sort { $a->pkg cmp $b->pkg } values %{ $self->{_pkgs} } );
+ }
+ return;
+}
+
+sub store($) {
+ my $self = shift;
+ my @pkgs = keys %{ $self->{_pkgs} };
+ my ( $cnt, $subcnt ) = $self->count;
+
+ print("\$pkgcnt = $cnt;\n");
+ print("\$subpkgcnt = $subcnt;\n");
+ map( $self->{_pkgs}{$_}->store, keys %{ $self->{_pkgs} } );
+}
+
+sub count($) {
+ my $self = shift;
+ my ( $pkgcnt, $pkgsubcnt );
+
+ map {
+ $pkgcnt++;
+ $pkgsubcnt += $self->{_pkgs}{$_}->count;
+ } keys %{ $self->{_pkgs} };
+ wantarray ? ( $pkgcnt, $pkgsubcnt ) : $pkgcnt;
+}
+
+# Pkgs is all versions of a given package (eg: apache-1.x and apache-2.x)
+#
+package Pkgs;
+
+sub add($@) {
+ my $self = shift;
+
+ $self->{_pkgver}{ $_[1] } = new PkgVer @_;
+}
+
+sub new($@) {
+ my $class = shift;
+ my $self = {};
+
+ bless $self, $class;
+ $self->{_pkg} = $_[0];
+ return $self;
+}
+
+sub versions($) {
+ my $self = shift;
+
+ return sort { $b cmp $a } keys %{ $self->{_pkgver} };
+}
+
+sub pkg($) {
+ my $self = shift;
+ $self->{_pkg};
+}
+
+sub pkgver($@) {
+ my $self = shift;
+
+ if (@_) {
+ if ( $self->{_pkgver}{ $_[0] } ) {
+ return ( $self->{_pkgver}{ $_[0] } );
+ }
+ return;
+ }
+ return sort { $b->ver() cmp $a->ver() } values %{ $self->{_pkgver} };
+}
+
+sub latestver($) {
+ my $self = shift;
+
+ ( $self->pkgver() )[0];
+}
+
+sub store($) {
+ my $self = shift;
+
+ print("\$pkgnum++;\n");
+ map( $self->{_pkgver}{$_}->store, keys %{ $self->{_pkgver} } );
+}
+
+sub count($) {
+ my $self = shift;
+
+ scalar( keys %{ $self->{_pkgver} } );
+}
+
+# PkgVer is a unique package+version
+#
+package PkgVer;
+
+sub new($$$) {
+ my $class = shift;
+ my $self = {};
+
+ bless $self, $class;
+ $self->{_pkg} = $_[0];
+ $self->{_ver} = $_[1];
+ return $self;
+}
+
+sub pkgname($) {
+ my $self = shift;
+
+ $self->pkg . '-' . $self->ver;
+}
+
+sub pkg($) {
+ my $self = shift;
+
+ $self->{_pkg};
+}
+
+sub var($$$) {
+ my $self = shift;
+ my ( $key, $val ) = @_;
+
+ ( defined $val )
+ ? ( $self->{$key} = $val )
+ : $self->{$key};
+}
+
+sub ver($) {
+ my $self = shift;
+
+ $self->{_ver};
+}
+
+sub vars($) {
+ my $self = shift;
+
+ grep( !/^_(pkg|ver)$/, keys %{$self} );
+}
+
+sub store($) {
+ my $self = shift;
+ my $data;
+
+ ( $data = $self->{_pkg} ) =~ s/([\\\$\@\%\"])/\\$1/g;
+ print("\$pkgver = \$pkglist->add(\"$data\", \"");
+
+ ( $data = $self->{_ver} ) =~ s/([\\\$\@\%\"])/\\$1/g;
+ print("$data\"); __pkgcount(1);\n");
+
+ foreach ( $self->vars ) {
+ ( $data = $self->{$_} ) =~ s/([\\\$\@\%\"])/\\$1/g;
+ print("\$pkgver->var(\"$_\", \"$data\");\n");
+ }
+}
+
+package main;
+
+main();