Index: head/devel/portlint/Makefile =================================================================== --- head/devel/portlint/Makefile (revision 110913) +++ head/devel/portlint/Makefile (revision 110914) @@ -1,45 +1,45 @@ # New ports collection makefile for: portlint # Date created: 13 Jun 1997 # Whom: Jun-ichiro itojun Hagino # # $FreeBSD$ # # This port is self contained in the src directory. # PORTNAME= portlint -PORTVERSION= 2.6.2 +PORTVERSION= 2.6.3 CATEGORIES= devel MASTER_SITES= # none DISTFILES= # none MAINTAINER= marcus@FreeBSD.org COMMENT= A verifier for FreeBSD port directory NO_BUILD= yes WRKSRC= ${WRKDIR}/src USE_PERL5= yes USE_REINPLACE= yes SRC= ${.CURDIR}/src MAN1= portlint.1 do-fetch: @${DO_NADA} pre-patch: @${CP} -R ${SRC} ${WRKDIR} post-patch: @${REINPLACE_CMD} -e 's|/usr/bin/perl|${PERL}|' ${WRKSRC}/portlint.pl @${REINPLACE_CMD} -e 's|/usr/bin/perl|${PERL}|' \ ${WRKSRC}/portlintgrep.pl do-install: ${INSTALL_SCRIPT} ${WRKSRC}/portlint.pl ${PREFIX}/bin/portlint ${INSTALL_MAN} ${WRKSRC}/portlint.1 ${MAN1PREFIX}/man/man1 ${MKDIR} ${EXAMPLESDIR} ${INSTALL_SCRIPT} ${WRKSRC}/portlintgrep.pl ${EXAMPLESDIR}/portlintgrep .include Property changes on: head/devel/portlint/Makefile ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.77 \ No newline at end of property +1.78 \ No newline at end of property Index: head/devel/portlint/src/portlint.pl =================================================================== --- head/devel/portlint/src/portlint.pl (revision 110913) +++ head/devel/portlint/src/portlint.pl (revision 110914) @@ -1,2411 +1,2443 @@ #! /usr/bin/perl # ex:ts=4 # # portlint - lint for port directory # implemented by: # Jun-ichiro itojun Hagino # Yoshishige Arai # # Copyright(c) 1997 by Jun-ichiro Hagino . # All rights reserved. # Freely redistributable. Absolutely no warranty. # # Please note that this perl code used to be able to handle (Open|Net|Free)BSD # bsd.port.mk. There are significant differences in those so you'll have # hard time upgrading this... # This code now mainly supports FreeBSD, but patches to update support for # OpenBSD and NetBSD will be accepted. # # $FreeBSD$ -# $Id: portlint.pl,v 1.47 2004/05/30 19:54:47 marcus Exp $ +# $Id: portlint.pl,v 1.48 2004/06/06 01:04:42 marcus Exp $ # use vars qw/ $opt_a $opt_A $opt_b $opt_c $opt_h $opt_t $opt_v $opt_M $opt_N $opt_B $opt_V /; use Getopt::Std; use File::Find; use IPC::Open2; #use strict; my ($err, $warn); my ($extrafile, $parenwarn, $committer, $verbose, $usetabs, $newport); my $contblank; my $portdir; my $makeenv = ""; $err = $warn = 0; $extrafile = $parenwarn = $committer = $verbose = $usetabs = $newport = 0; $contblank = 1; $portdir = '.'; # version variables my $major = 2; my $minor = 6; -my $micro = 2; +my $micro = 3; sub l { '[{(]'; } sub r { '[)}]'; } sub s { '[ \t]'; } my $l = &l; my $r = &r; my $s = &s; # default setting - for FreeBSD my $portsdir = '/usr/ports'; my $rcsidstr = 'FreeBSD'; my $multiplist = 0; my $ldconfigwithtrue = 0; my $rcsidinplist = 0; my $mancompress = 1; my $manstrict = 0; my $newxdef = 1; my $automan = 1; my $autoinfo = 1; my $use_no_size = 0; +my $use_no_checksum = 0; my $manchapters = '123456789ln'; my $localbase = '/usr/local'; my %lang_pref = qw( arabic ar chinese zh french fr german de hebrew iw hungarian hu japanese ja korean ko polish pl portuguese pt russian ru ukrainian uk vietnamese vi ); my @lang_cat = keys %lang_pref; my @lang_short = values %lang_pref; my $re_lang_short = '(' . join('|', @lang_short) . ')-'; my ($prog) = ($0 =~ /([^\/]+)$/); sub usage { print STDERR <); close(MK); $cmd = join(' -V MASTER_SITE_', "make $makeenv -f - all", @site_groups); $i = 0; open2(IN, OUT, $cmd); print OUT <) { my $g = $site_groups[$i]; for my $s (split()) { $predefined{$s} = $g; } $i++; } close(IN); # # check for files. # my @checker = ($makevar{DESCR}, 'Makefile', $makevar{MD5_FILE}); my %checker = ( $makevar{DESCR} => 'checkdescr', 'Makefile' => 'checkmakefile', $makevar{MD5_FILE} => 'checkdistinfo', ); if ($extrafile) { my @files = ( <$makevar{SCRIPTDIR}/*>, @makevar{DESCR,PLIST,PKGINSTALL,PKGDEINSTALL,PKGREQ,PKGMESSAGE} ); foreach my $i (@files) { next if (! -T $i); next if (defined $checker{$i}); if ($i =~ /\bpkg-plist$/ || ($multiplist && $i =~ /\bpkg-plist/)) { unshift(@checker, $i); $checker{$i} = 'checkplist'; } else { push(@checker, $i); $checker{$i} = 'checkpathname'; } } } foreach my $i (<$makevar{PATCHDIR}/patch-*>) { next if (! -T $i); next if (defined $checker{$i}); push(@checker, $i); $checker{$i} = 'checkpatch'; } foreach my $i (@checker) { print "OK: checking $i.\n" if ($verbose); if (! -f "$i") { &perror("FATAL: no $i in \"$portdir\".") unless $i eq $makevar{MD5_FILE} && $makevar{DISTFILES} eq ""; } else { my $proc = $checker{$i}; &$proc($i) || &perror("Cannot open the file $i\n"); if ($proc ne 'checkpatch') { &checklastline($i) || &perror("Cannot open the file $i\n"); } } } # Check to make sure there is no pkg-comment file anymore. if (-f 'pkg-comment') { &perror("FATAL: Use of pkg-comment is obsolete. Use the COMMENT macro within the port's Makefile instead."); } if ($committer) { sub find_proc { return if /^\.\.?$/; (my $fullname = $File::Find::name) =~ s#^\./##; print "OK: checking the file name of $fullname.\n" if ($verbose); if ($fullname eq 'work') { &perror("FATAL: $fullname: be sure to cleanup the working directory ". "before committing the port."); $File::Find::prune = 1; } elsif (-l) { &perror("Warning: $fullname: this is a symlink. ". "CVS will ignore it."); } elsif (-z) { &perror("FATAL: $fullname: empty file and should be removed. ". "If it still needs to be there, put a dummy comment ". "to state that the file is intentionally left empty."); } elsif (-d && scalar(my @x = <$_/{*,.?*}>) <= 1) { &perror("FATAL: $fullname: empty directory should be removed."); } elsif (/^\./) { &perror("Warning: $fullname: dotfiles are not preferred. ". "If this file is a dotfile to be installed as an example, ". "consider importing it as \"dot$_\"."); } elsif (/\.(orig|rej|bak)$/ || /~$/ || /^\#/) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "backup files before committing the port."); } elsif (/(^|\.)core$/) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "core files before committing the port."); } elsif ($_ eq 'CVS' && -d) { if ($newport) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "CVS directories before importing the new port."); } $File::Find::prune = 1; } } find(\&find_proc, '.'); sub checksubdir { my $dir = shift; print "OK: checking CVS status of \"$dir\".\n" if ($verbose); opendir DIR, $dir; my @filenames = readdir DIR; closedir DIR; my %entries; if (-f "$dir/CVS/Entries") { open ENTRIES, "<$dir/CVS/Entries"; while () { chomp; my @entry = split /\//; if ($entry[0] eq 'D') { $entries{ $entry[1] } = $entry[0] if $entry[1]; } elsif ($entry[0] eq '') { if ($entry[2] =~ /^-/) { $entries{ $entry[1] } = 'x'; } elsif ($entry[2] eq '0') { $entries{ $entry[1] } = 'n'; } else { $entries{ $entry[1] } = 'f'; } } else { &perror("WARN: can not parse CVS line $_"); } } close ENTRIES; } else { &perror("WARN: no CVS directories. Use -N to check a new port."); return; } if (-f "$dir/CVS/Entries.Log") { open ENTRIES, "<$dir/CVS/Entries.Log"; while () { chomp; my $cmd; my @entry = split /\//; if (/^(.) (.*)$/) { $cmd = $1; @entry = split /\//, $2; } else { $cmd = 'A'; @entry = split /\//; } if ($cmd eq 'A') { if ($entry[0] eq 'D') { $entries{ $entry[1] } = $entry[0] if $entry[1]; } elsif ($entry[0] eq '') { if ($entry[2] =~ /^-/) { $entries{ $entry[1] } = 'x'; } elsif ($entry[2] eq '0') { $entries{ $entry[1] } = 'n'; } else { $entries{ $entry[1] } = 'f'; } } else { &perror("WARN: can not parse CVS line $_"); } } elsif ($cmd eq 'R') { delete $entries{ $entry[1] } if $entry[1]; } # ignore unknown commands } close ENTRIES; } foreach (@filenames) { next if /^(?:\.\.?|CVS)$/; my $filename = $dir eq '.' ? $_ : "$dir/$_"; if (-d $filename) { if (!$entries{$_} || $entries{$_} ne 'D') { &perror("FATAL: directory $filename not in CVS."); } else { delete $entries{$_}; checksubdir($filename); } } else { if (!$entries{$_}) { &perror("FATAL: file $filename not in CVS.") unless (eval { /$ENV{'PL_CVS_IGNORE'}/, 1 } && /$ENV{'PL_CVS_IGNORE'}/); } elsif ($entries{$_} eq 'D') { &perror("FATAL: file $filename is a directory in CVS."); } elsif ($entries{$_} eq 'x') { &perror("FATAL: file $filename is deleted in CVS."); } elsif ($entries{$_} eq 'n') { if (!system("egrep", "-q", "\\\$$rcsidstr\[^\$\]+\\\$", $filename)) { &perror("WARN: RCS tag \"\$$rcsidstr\$\" ". "should be empty in new file $filename."); } delete $entries{$_}; } else { delete $entries{$_}; } } } while (my ($file, $type) = each %entries) { next if $type eq 'x'; if ($type eq 'D') { &perror("FATAL: CVS directory $dir/$file missing"); } else { &perror("FATAL: CVS file $dir/$file missing"); } } } checksubdir('.') unless $newport; # Check for ports that may break INDEX my $indexerr = `env LOCALBASE=/nonexistentlocal X11BASE=/nonexistentx make $makeenv describe 2>&1 >/dev/null`; chomp $indexerr; $indexerr =~ tr/\n/ /s; &perror("FATAL: breaks INDEX ($indexerr).") if ($indexerr); } if ($err || $warn) { print "$err fatal errors and $warn warnings found.\n" } else { print "looks fine.\n"; } exit $err; # # distinfo # sub checkdistinfo { my($file) = @_; my($sizefound) = 0; + my(@distinfo) = (); + my(@distfiles) = (); + open(IN, "< $file") || return 0; - while () { - if ($_ =~ /^SIZE/) { - $sizefound = 1; - } - } - if (!$sizefound && !$use_no_size) { - &perror("WARN: $file: does not contain SIZE."); - } + @distinfo = ; close(IN); + + @distfiles = split(/\s+/, $makevar{DISTFILES}); + + foreach my $distfile (@distfiles) { + if (!(grep /^SIZE \(([^\)]*\/)?$distfile\)/, @distinfo) && + !$use_no_size) { + &perror("WARN: $file: no SIZE entry found for $distfile."); + } + if (!(grep /^MD5 \(([^\)]*\/)?$distfile\)/, @distinfo) && + !$use_no_checksum) { + &perror("WARN: $file: no MD5 entry found for $distfile."); + } + } + + 1; } # # pkg-descr # sub checkdescr { my($file) = @_; my(%maxchars) = ($makevar{DESCR}, 80); my(%maxlines) = ($makevar{DESCR}, 24); my(%errmsg) = ($makevar{DESCR}, "exceeds $maxlines{$makevar{DESCR}} ". "lines, make it shorter if possible."); my($longlines, $linecnt, $tmp) = (0, 0, ""); open(IN, "< $file") || return 0; while () { $tmp .= $_; chomp || &perror("WARN: $file: should terminate in '\n'."); $linecnt++; $longlines++ if ($maxchars{$file} < length); } if ($linecnt > $maxlines{$file}) { &perror("WARN: $file $errmsg{$file}". "(currently $linecnt lines)"); } else { print "OK: $file: has $linecnt lines.\n" if ($verbose); } if ($longlines > 0) { &perror("WARN: $file: includes lines that exceed $maxchars{$file} ". "characters."); } if ($tmp =~ /[\033\200-\377]/) { &perror("WARN: $file: includes iso-8859-1, or ". "other local characters. files should be in ". "plain 7-bit ASCII"); } if ($file =~ /\bpkg-descr/ && $tmp =~ m,http://,) { my $has_url = 0; my $has_www = 0; foreach my $line (grep($_ =~ "http://", split(/\n+/, $tmp))) { $has_url = 1; if ($line =~ m,WWW:[ \t]+http://,) { $has_www = 1; } } if ($has_url && ! $has_www) { &perror("FATAL: $file: contains a URL but no \"WWW:\""); } } close(IN); } # # pkg-plist # sub checkplist { my($file) = @_; my($curdir) = ($localbase); my(%omfremoveseen) = (); my(%omfinstallseen) = (); my(%omfseen) = (); my($inforemoveseen, $infoinstallseen, $infoseen) = (0, 0, 0); my(%omfafterinstall) = (); my(%omfafterremove) = (); my($infobeforeremove, $infoafterinstall) = (0, 0); my($infooverwrite) = (0); my($rcsidseen) = (0); my(@exec_omf) = (); my(@exec_info) = (); my(@unexec_omf) = (); my(@unexec_info) = (); my(@omffile) = (); my(@infofile) = (); my $seen_dirrm_docsdir; open(IN, "< $file") || return 0; while () { if ($_ =~ /[ \t]+\n?$/) { &perror("WARN: $file [$.]: whitespace before end ". "of line."); } # make it easier to handle. $_ =~ s/\s+$//; $_ =~ s/\n$//; if ($osname eq 'NetBSD' && $_ =~ /<\$ARCH>/) { &perror("WARN: $file [$.]: use of <\$ARCH> deprecated, ". "use \${MACHINE_ARCH} instead."); } if (m'lib/perl5/site_perl/%%PERL_VER%%') { &perror("WARN: $file [$.]: use \%\%SITE_PERL\%\% ". "instead of lib/perl5/site_perl/\%\%PERL_VER\%\%."); } $seen_dirrm_docsdir++ if /^(\%\%PORTDOCS\%\%)?\@dirrm\s+\%\%DOCSDIR\%\%/ || /^(\%\%PORTDOCS\%\%)?\@unexec\s+(\/bin\/)?rmdir\s+\%D\/\%\%DOCSDIR\%\%\s+2\>\s*\/dev\/null\s+\|\|\s+(\/usr\/bin\/)?true/; if ($_ =~ /^\@/) { if ($_ =~ /^\@(cwd|cd)[ \t]+(\S+)/) { $curdir = $2; } elsif ($_ =~ /^\@unexec[ \t]+rm[ \t]/) { if ($_ !~ /%[DB]/) { &perror("WARN: $file [$.]: use \"%D\" or \"%B\" to ". "specify prefix."); } if ($_ !~ /true$/ && $_ !~ /rm -f/) { &perror("WARN: $file [$.]: add \"2>&1 ". ">/dev/null || true\" ". "to \"\@unexec rm\"."); } } elsif ($_ =~ /^\@unexec[ \t]+rmdir/) { if ($_ !~ /%[DB]/) { &perror("WARN: $file [$.]: use \"%D\" or \"%B\" to ". "specify prefix."); } if ($_ !~ /true$/) { &perror("WARN: $file [$.]: use \"\@dirrm\" ". "instead of \"\@unexec rmdir\"."); } } elsif ($_ =~ /^\@exec[ \t]+scrollkeeper-install[ \t]+-q\s+(\S+)\s+.+$/) { push(@exec_omf, $1); my $ot = $1; $ot =~ s/^\%D\///; $omfinstallseen{$ot} = $.; } elsif (!$autoinfo && $_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { $infoinstallseen = $.; push(@exec_info, $1); } elsif ($autoinfo && $_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { &perror("WARN: $file [$.]: \@exec install-info is deprecated in favor of adding info files into the Makefile using the INFO macro."); } elsif ($_ =~ /^\@unexec[ \t]+scrollkeeper-uninstall[ \t]+-q\s+(\S+)\s+.+$/) { push(@unexec_omf, $1); my $ot = $1; $ot =~ s/^\%D\///; $omfremoveseen{$ot} = $.; } elsif (!$autoinfo && $_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { $inforemoveseen = $.; push(@unexec_info, $1); } elsif ($autoinfo && $_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { &perror("WARN: $file [$.]: \@unexec install-info is deprecated in favor of adding info files into the Makefile using the INFO macro."); } elsif ($_ =~ /^\@(exec|unexec)/) { if (/ldconfig/) { if ($ldconfigwithtrue && !/\/usr\/bin\/true/) { &perror("FATAL: $file [$.]: ldconfig ". "must be used with ". "\"||/usr/bin/true\"."); } &perror("WARN: $file [$.]: possible ". "direct use of ldconfig ". "in PLIST found. use ". "INSTALLS_SHLIB instead."); } } elsif ($_ =~ /^\@(comment)/) { $rcsidseen++ if (/\$$rcsidstr[:\$]/); } elsif ($_ =~ /^\@(owner|group)\s/) { &perror("WARN: $file [$.]: \@$1 should not be needed"); } elsif ($_ =~ /^\@(dirrm|option)/) { ; # no check made } else { &perror("WARN: $file [$.]: ". "unknown pkg-plist directive \"$_\""); } next; } if ($_ =~ /^\//) { &perror("FATAL: $file [$.]: use of full pathname ". "disallowed."); } - if ($_ =~ /\.la$/ && $makevar{USE_LIBTOOL} eq '' && - $makevar{USE_LIBTOOL_VER} eq '') { - &perror("WARN: $file [$.]: installing libtool archives, ". + if ($_ =~ /\.la$/ && $makevar{USE_LIBTOOL_VER} eq '') { + &perror("WARN: $file [$.]: installing libtool archives, ". "please use USE_LIBTOOL_VER in Makefile if possible. ". "See http://www.FreeBSD.org/gnome/docs/portlint.html ". "for a way to completely eliminate .la files."); } if ($_ =~ m|^lib/lib[^\/]+\.so(\.\d+)?$| && $makevar{INSTALLS_SHLIB} eq '') { &perror("WARN: $file [$.]: installing shared libraries, ". "please define INSTALLS_SHLIB as appropriate"); } if ($autoinfo && $_ =~ /\.info$/) { &perror("WARN: $file [$.]: enumerating info files in the plist is deprecated in favor of adding info files into the Makefile using the INFO macro."); } if ($autoinfo && $_ =~ /\.info-\d+$/) { &perror("FATAL: $file [$.]: numbered info files are obsolete and not portable; add info files using the INFO macro in the Makefile."); } if ($_ =~ /.*\.omf$/) { $omfseen{$_} = $.; $omfafterinstall{$_}++ if ($omfinstallseen{$_}); $omfafterremove{$_}++ if ($omfremoveseen{$_}); push(@omffile, $_); } if (!$autoinfo) { if ($_ =~ /^info\/.*info(-[0-9]+)?$/) { $infoseen = $.; $infoafterinstall++ if ($infoinstallseen); $infobeforeremove++ if (!$inforemoveseen); push(@infofile, $_); } if ($_ =~ /^info\/dir$/) { &perror("FATAL: $file [$.]: \"info/dir\" should not be listed.". "Use install-info to add/remove ". "an entry."); $infooverwrite++; } } if ($_ =~ /^(\%\%PORTDOCS\%\%)?share\/doc\//) { &perror("WARN: $file [$.]: consider using DOCSDIR macro"); $sharedocused++; } elsif ($_ =~ /^(\%\%PORTDOCS\%\%)?\%\%DOCSDIR\%\%/) { $sharedocused++; } if ($_ =~ /^share\/examples\//) { &perror("WARN: $file [$.]: consider using EXAMPLESDIR macro"); } if ($_ =~ m#man/([^/]+/)?man([$manchapters])/([^\.]+\.[$manchapters])(\.gz)?$#) { if ($4 eq '') { $plistman{$2} .= ' ' . $3; if ($mancompress) { &perror("FATAL: $file [$.]: ". "unpacked man file $3 ". "listed. must be gzipped."); } } else { $plistmangz{$2} .= ' ' . $3; if (!$mancompress) { &perror("FATAL: $file [$.]: ". "gzipped man file $3$4 ". "listed. unpacked one should ". "be installed."); } } $plistmanall{$2} .= ' ' . $3; if ($1 ne '') { $manlangs{substr($1, 0, length($1) - 1)}++; } } if ($curdir !~ m#^$localbase# && $curdir !~ m#^/usr/X11R6#) { &perror("WARN: $file [$.]: installing to ". "directory $curdir discouraged. ". "could you please avoid it?"); } if ("$curdir/$_" =~ m#^$localbase/share/doc#) { print "OK: $file [$.]: seen installation to share/doc. ". "($curdir/$_)\n" if ($verbose); $sharedocused++; } } if ($sharedocused && !$seen_dirrm_docsdir) { &perror("WARN: $file: Both ``\%\%PORTDOCS\%\%\@dirrm \%\%DOCSDIR\%\%'' and ``\%\%PORTDOCS\%\%\@unexec \%D/\%\%DOCSDIR\%\% 2>/dev/null || true'' are missing. At least one should be used."); } # Check that each OMF file has an install and deinstall line. my $omf_install = join(" ", @exec_omf); $omf_install .= ' '; my $omf_deinstall = join(" ", @unexec_omf); $omf_deinstall .= ' '; foreach my $of (@omffile) { if ($omf_install !~ /\%D\/\Q$of\E/) { &perror("FATAL: $file: you need an '\@exec scrollkeeper-install -q \%D/$of 2>/dev/null || /usr/bin/true' line"); } if ($omf_deinstall !~ /\%D\/$of/) { &perror("FATAL: $file: you need an '\@unexec scrollkeeper-uninstall -q \%D/$of 2>/dev/null || /usr/bin/true' line"); } } if (!$autoinfo) { # check that every infofile has an exec install-info and unexec install-info my $exec_install = join(" ", @exec_info); $exec_install .= ' '; my $unexec_install = join(" ", @unexec_info); $unexec_install .= ' '; foreach my $if (@infofile) { next if ($if =~ m/info-/); if ($exec_install !~ m/\%D\/\Q$if\E/) { &perror("FATAL: $file: you need an '\@exec install-info \%D/$if \%D/info/dir' line"); } if ($unexec_install !~ m/\%D\/$if/) { &perror("FATAL: $file: you need an '\@unexec install-info --delete \%D/$if \%D/info/dir' line"); } } } if ($rcsidinplist && !$rcsidseen) { &perror("FATAL: $file: RCS tag \"\$$rcsidstr\$\" must be present ". "as \@comment.") } if (((!$autoinfo && !$infoseen) || $autoinfo) && !scalar(keys %omfseen)) { close(IN); return 1; } if (scalar(keys %omfseen)) { if (!scalar(keys %omfinstallseen)) { &perror("FATAL: $file: scrollkeeper-install must be used to ". "add/delete entries from the ScrollKeeper OMF database."); } else { foreach my $of (keys %omfseen) { if ($omfafterinstall{$of}) { &perror("FATAL: $file [$omfinstallseen{$of}]: move ". "\"\@exec scrollkeeper-install\" ". "line to make sure that it is placed after ". "the $of entry."); } } } if (!scalar(keys %omfremoveseen)) { &perror("FATAL: $file: \"\@unexec scrollkeeper-uninstall\" must ". "be placed after the OMF file it uninstalls."); } else { foreach my $of (keys %omfseen) { if ($omfafterremove{$of}) { &perror("FATAL: $file [$omfremoveseen{$of}]: move ". "\"\@unexec scrollkeeper-uninstall\" ". "line to make sure that it is placed after ". "the $of entry."); } } } } if (!$autoinfo && $infoseen) { if (!$infoinstallseen) { if ($infooverwrite) { &perror("FATAL: $file: install-info must be used to ". "add/delete entries into \"info/dir\"."); } &perror("FATAL: $file: \"\@exec install-info \%D/... \%D/info/dir\" must be placed ". "after all the info files."); } elsif ($infoafterinstall) { &perror("FATAL: $file [$infoinstallseen]: move ". "\"\@exec install-info\" line to make ". "sure that it is placed after all the info files."); } if (!$inforemoveseen) { &perror("FATAL: $file: \"\@unexec install-info --delete \%D/... \%D/info/dir\" must ". "be placed before any of the info files listed."); } elsif ($infobeforeremove) { &perror("FATAL: $file [$inforemoveseen]: move ". "\"\@exec install-info --delete\" ". "line to make sure ". "that it is placed before any of the info files. "); } } close(IN); } # # misc files # sub checkpathname { my($file) = @_; my($whole); open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } &abspathname($whole, $file); close(IN); } sub checklastline { my($file) = @_; my($whole); open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } if ($whole !~ /\n$/) { &perror("FATAL: $file: the last line has to be ". "terminated by \\n."); } if ($whole =~ /\n([ \t]*\n)+$/) { &perror("WARN: $file: seems to have unnecessary blank lines ". "at the last part."); } close(IN); } sub checkpatch { my($file) = @_; my($whole); if (-z "$file") { &perror("FATAL: $file: has no content. should be removed ". "from repository."); return; } open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } if ($committer && $whole =~ /\$([A-Z][A-Za-z0-9]+)(:[^\n]+)?\$/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: includes possible RCS tag \"\$$1\$\". ". "use binary mode (-ko) on commit/import.") unless $1 eq $rcsidstr; } close(IN); } # # Makefile # sub checkmakefile { my($file) = @_; my($rawwhole, $whole, $idx, @sections); my($i, $j, $k, $l); my @cat = (); my $has_lang_cat = 0; my $port_lang = ''; my $tmp; my $bogusdistfiles = 0; my @varnames = (); my($portname, $portversion, $distfiles, $distname, $extractsufx) = ('', '', '', '', ''); my $masterport = 0; my $slaveport = 0; my $use_gnome_hack = 0; my($realwrksrc, $wrksrc, $nowrksubdir) = ('', '', ''); my(@mman, @pman); my($pkg_version, $versiondir, $versionfile) = ('', '', ''); my $useindex = 0; + my %deprecated = (); open(IN, "< $file") || return 0; $rawwhole = ''; $tmp = 0; while () { if ($_ =~ /[ \t]+\n?$/) { &perror("WARN: $file [$.]: whitespace before ". "end of line."); } if ($_ =~ /^ /) { # 8 spaces here! &perror("WARN: $file [$.]: use tab (not space) to make ". "indentation"); } if ($usetabs) { if (m/^[A-Za-z0-9_-]+.?= /) { if (m/[?+]=/) { &perror("WARN: $file [$.]: use a tab (not space) after a ". "variable name"); } else { &perror("FATAL: $file [$.]: use a tab (not space) after a ". "variable name"); } } } # # I'm still not very convinced, for using this kind of magical word. # 1. This kind of items are not important for Makefile; # portlint should not require any additional rule to Makefile. # portlint should simply implement items that are declared in Handbook. # 2. If we have LINTSKIP, we can't stop people using LINTSKIP too much. # IMHO it is better to warn the user and let the user think twice, # than let the user escape from portlint. # Uncomment this part if you are willing to use these magical words. # Thu Jun 26 11:37:56 JST 1997 # -- itojun # # if ($_ =~ /^# LINTSKIP\n?$/) { # print "OK: skipping from line $. in $file.\n" # if ($verbose); # $tmp = 1; # next; # } # if ($_ =~ /^# LINTAGAIN\n?$/) { # print "OK: check start again from line $. in $file.\n" # if ($verbose); # $tmp = 0; # next; # } # if ($_ =~ /# LINTIGNORE/) { # print "OK: ignoring line $. in $file.\n" if ($verbose); # next; # } # next if ($tmp); $rawwhole .= $_; } close(IN); # # whole file: blank lines. # $whole = "\n" . $rawwhole; study $whole; print "OK: checking contiguous blank lines in $file.\n" if ($verbose); $i = "\n" x ($contblank + 2); if ($whole =~ /$i/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: contiguous blank lines ". "(> $contblank lines) found."); } # # whole file: $(VARIABLE) # if ($parenwarn) { print "OK: checking for \$(VARIABLE).\n" if ($verbose); if ($whole =~ /\$\([\w\d]+\)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use \${VARIABLE}, instead of ". "\$(VARIABLE)."); } } # # whole file: PLIST_FILES and PLIST_DIRS # print "OK: checking PLIST_FILES and PLIST_DIRS.\n" if ($verbose); if ($whole =~ /\nPLIST_FILES.?=/ || $whole =~ /\nPLIST_DIRS.?=/) { if (-f 'pkg-plist') { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: You may remove pkg-plist ". "if you use PLIST_FILES and/or PLIST_DIRS."); } } # # whole file: USE_* used too late # pos($whole) = 0; if ($whole =~ /^\.include\s+$/gm) { print "OK: checking for USE_* used too late.\n" if ($verbose); my @use_early = qw( APACHE BZIP2 GNUSTEP IMAKE JAVA KDE(?:BASE|LIBS)_VER (?:LIB)?RUBY LINUX_PREFIX OPENSSL PHP PYTHON QT2? QT_VER X_PREFIX ZIP ); my @other_early = qw( EMACS_PORT_NAME OPTIONS ); my $earlypattern = join('|', 'USE_(?:'.join('|', @use_early).')', @other_early); while ($whole =~ /^($earlypattern)[+?:!]?=/gmo) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: $1 is set after ". "including bsd.port.pre.mk."); } } # # whole file: USE_* as a user-settable option # print "OK: checking for USE_* as a user-settable option.\n" if ($verbose); while ($whole =~ /\n\s*\.\s*(?:el)?if[^\n]*?\b(\w*USE_)(\w+)(?\![^\n]*\n#?\.error)/g) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: is $1$2 a user-settable option? ". "Consider using WITH_$2 instead.") if ($1.$2 ne 'USE_GCC'); } # # whole file: NO_CHECKSUM # # XXX Don't compress newlines since it messes up line number calculation. #$whole =~ s/\n#[^\n]*/\n/g; #$whole =~ s/\n\n+/\n/g; print "OK: checking NO_CHECKSUM.\n" if ($verbose); if ($whole =~ /\nNO_CHECKSUM/) { + $use_no_checksum = 1; my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: use of NO_CHECKSUM discouraged. ". "it is intended to be a user variable."); } # # whole file: USE_SIZE # print "OK: checking USE_SIZE.\n" if ($verbose); if ($whole =~ /\nUSE_SIZE/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use of USE_SIZE is no longer ". "required."); } # # whole file: MACHINE_ARCH # print "OK: checking MACHINE_ARCH.\n" if ($verbose); if ($whole =~ /\nMACHINE_ARCH/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: MACHINE_ARCH should never be ". "overridden."); } # # whole file: PKGNAME # print "OK: checking PKGNAME.\n" if ($verbose); if ($whole =~ /\nPKGNAME.?=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: PKGNAME is obsoleted by PORTNAME, ". "PORTVERSION, PKGNAMEPREFIX and PKGNAMESUFFIX."); } # # whole file: IS_INTERACTIVE/NOPORTDOCS # print "OK: checking IS_INTERACTIVE.\n" if ($verbose); if ($whole =~ /\nIS_INTERACTIVE/) { if ($whole !~ /defined\((BATCH|FOR_CDROM)\)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use of IS_INTERACTIVE ". "discouraged. provide batch mode by using BATCH and/or ". "FOR_CDROM."); } } print "OK: checking for use of NOPORTDOCS.\n" if ($verbose); if ($whole =~ /NOPORTSDOC/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: NOPORTSDOC found. Do you ". "mean NOPORTDOCS?"); } if ($sharedocused && $whole !~ /defined\s*\(?NOPORTDOCS\)?/ && $whole !~ /def\s*\(?NOPORTDOCS\)?/ && $whole !~ m#(\$[\{\(]PREFIX[\}\)]|$localbase)/share/doc#) { &perror("WARN: $file: use \".if !defined(NOPORTDOCS)\" to wrap ". "installation of files into $localbase/share/doc."); } # # whole file: check for USE_GETTEXT # print "OK: checking for USE_GETTEXT without WITHOUT_NLS.\n" if ($verbose); if ($whole =~ /\nUSE_GETTEXT/ && $whole !~ /def(?:ined)?\s*\(?WITHOUT_NLS\)?/) { &perror("WARN: $file: Consider adding support for a WITHOUT_NLS ". "knob to conditionally disable gettext support."); } # + # whole file: check for deprecated commands + # + print "OK: checking for deprecated macros.\n" if $verbose; + %deprecated = ( + USE_LIBTOOL => 'USE_LIBTOOL_VER', + USE_AUTOCONF => 'USE_AUTOHEADER_VER', + USE_AUTOMAKE => 'USE_AUTOMAKE_VER', + WANT_LIBTOOL => 'WANT_LIBTOOL_VER', + WANT_AUTOCONF => 'WANT_AUTOCONF_VER', + WANT_AUTOMAKE => 'WANT_AUTOMAKE_VER', + USE_MESA => 'USE_GL', + ); + + for my $depmacro (keys %deprecated) { + if ($whole =~ /\n($depmacro)[+?:!]?=/) { + &perror("FATAL: $file: $depmacro is ". + "deprecated, use $deprecated{$1} instead"); + } + } + + # # whole file: direct use of command names # my %cmdnames = (); print "OK: checking direct use of command names.\n" if ($verbose); foreach my $i (qw( awk basename brandelf cat chmod chown cp cpio dialog dirname echo egrep expr false file find gmake grep gzcat ldconfig ln md5 mkdir mv objcopy paste patch pax perl printf rm rmdir ruby sed sh sort touch tr which xargs xmkmf )) { $cmdnames{$i} = "\$\{\U$i\E\}"; } $cmdnames{'env'} = '${SETENV}'; $cmdnames{'gunzip'} = '${GUNZIP_CMD}'; $cmdnames{'gzip'} = '${GZIP_CMD}'; $cmdnames{'install'} = '${INSTALL_foobaa}'; $cmdnames{'python'} = '${PYTHON_CMD}'; $cmdnames{'strip'} = '${STRIP_CMD}'; $cmdnames{'unzip'} = '${UNZIP_CMD}'; foreach my $i (qw(aclocal autoconf autoheader automake autoreconf autoupdate autoscan ifnames libtool libtoolize)) { $autocmdnames{$i} = "\$\{" . ( ( $i !~ /auto|aclocal|libtool/ ) ? "AUTO" : "" ) . "\U$i\E\}"; } # # ignore parameter string to echo command. # note that we leave the command as is, since we need to check the # use of echo itself. $j = $whole; $j =~ s/([ \t][\@\-]{0,2})(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+(?:"(?:\\'|\\"|[^"])*"|'(?:\\'|\\"|[^'])*')[ \t]*;?(\n?)/$1$2;$3/g; #" # ignore variables names in .for loops, but not what's at the end # of the for loop $j =~ s/(\.for +)([^ ]*)( .*)/$1$3/; foreach my $i (keys %cmdnames) { # XXX This is a hack. Really, we should break $j up into individual # lines, and go through each one. while ($j =~ /^(.*$i.*)$/gm) { my $curline = $1; my $lineno = &linenumber($`); if ($curline =~ /(^|\s+)[\@\-]{0,2}$i\b/ && $curline !~ /^[A-Z]+_TARGET[?+]?=[^\n]+$i/m && $curline !~ /^IGNORE(.)?=[^\n]+$i/m && $curline !~ /^BROKEN(.)?=[^\n]+$i/m && $curline !~ /^RESTRICTED(.)?=[^\n]+$i/m && $curline !~ /^NO_PACKAGE(.)?=[^\n]+$i/m && $curline !~ /^NO_CDROM(.)?=[^\n]+$i/m && $curline !~ /^MAINTAINER(.)?=[^\n]+$i/m && $curline !~ /^CATEGORIES(.)?=[^\n]+$i/m && $curline !~ /^#.+$/m && $curline !~ /^COMMENT(.)?=[^\n]+$i/m) { &perror("WARN: $file [$lineno]: possible direct use of ". "command \"$i\" found. use ". "$cmdnames{$i} instead."); } } } foreach my $i (keys %autocmdnames) { # XXX Same hack as above. while ($j =~ /^(.*($i\d*).*)$/gm) { my $lm = $1; my $sm = $2; my $lineno = &linenumber($`); if ($lm =~ /(^|\s+)[\@\-]{0,2}($i\d*)\b/ && $lm !~ /^[A-Z]+_TARGET[?+]?=[^\n]+($i\d*)/m && $lm !~ /^IGNORE(.)?=[^\n]+($i\d*)/m && $lm !~ /^BROKEN(.)?=[^\n]+($i\d*)/m && $lm !~ /^RESTRICTED(.)?=[^\n]+($i\d*)/m && $lm !~ /^NO_PACKAGE(.)?=[^\n]+($i\d*)/m && $lm !~ /^NO_CDROM(.)?=[^\n]+($i\d*)/m && $lm !~ /^MAINTAINER(.)?=[^\n]+($i\d*)/m && $lm !~ /^CATEGORIES(.)?=[^\n]+($i\d*)/m && $lm !~ /^#.+$/m && $lm !~ /^COMMENT(.)?=[^\n]+($i\d*)/m) { &perror("WARN: $file [$lineno]: possible direct use of ". "command \"$sm\" found. Use $autocmdnames{$i} ". "instead and set according USE_*_VER= flag"); } } } # # whole file: ldconfig must come with "true" command # if ($ldconfigwithtrue && $j =~ /(ldconfig|\$[{(]LDCONFIG[)}])/ && $j !~ /(\/usr\/bin\/true|\$[{(]TRUE[)}])/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: ldconfig must be used with ". "\"||\${TRUE}\"."); } # # whole file: ${GZIP_CMD} -9 (or any other number) # if ($j =~ /\${GZIP_CMD}\s+-(\w+(\s+-)?)*(\d)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${GZIP_CMD} -$3\" ". "found. \${GZIP_CMD} includes \"-\${GZIP}\" which ". "sets the compression level."); } # # whole file: ${MKDIR} -p # if ($j =~ /\${MKDIR}\s+-p/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${MKDIR} -p\" ". "found. \${MKDIR} includes ". "\"-p\" by default."); } # # whole file: ${MACHINE_ARCH} # if ($j =~ /\${MACHINE_ARCH}\s*[!=]=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: MACHINE_ARCH should never be tested ". "directly; use ARCH instead."); } # # whole file: full path name # &abspathname($whole, $file); # # whole file: SITE_PERL # print "OK: checking SITE_PERL.\n" if ($verbose); if ($whole =~ /\nSITE_PERL[?:]?=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: use of SITE_PERL discouraged. ". "it is set in bsd.port.mk."); } # # whole file: ${LOCALBASE}/lib/perl5/site_perl/${PERL_VER} # if ($j =~ m'\${(?:LOCALBASE|PREFIX)}/lib/perl5/site_perl/\${PERL_VER}') { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${LOCALBASE}/lib/perl5/site_perl/\${PERL_VER}\" ". "found. use \"\${SITE_PERL}\" instead."); } # # whole file: USE_GNOME check # if ($whole =~ /^USE_GNOME[?:]?=\s*(.*)$/m) { if ($1 =~ /gnomehack/) { $use_gnome_hack = 1; } } # # whole file: check for NO_SIZE # if ($whole =~ /^NO_SIZE[?:]?=/m) { $use_no_size = 1; } # - # whole file: check for deprecated USE_MESA - # - if ($whole =~ /^USE_MESA[?:]?=/m) { - my $lineno = &linenumber($`); - &perror("WARN: $file [$lineno]: USE_MESA is deprecated". - ". use USE_GL instead."); - } - - # # slave port check # my $masterdir = $makevar{MASTERDIR}; if ($masterdir ne '' && $masterdir ne $makevar{'.CURDIR'}) { $slaveport = 1; print "OK: slave port detected, checking for inclusion of $masterdir/Makefile.\n" if ($verbose); if ($whole =~ /^\.\s*include\s*[<"]bsd\.port(?:\.post)?\.mk[">]/m) { &perror("FATAL: $file: supposedly non-slave port with". " .CURDIR != MASTERDIR"); } elsif ($whole =~ /^\.\s*include\s*[<"]bsd\.port\.pre\.mk[">]/m) { &perror("FATAL: $file: slave ports may not include". " bsd.port.pre.mk"); } if ($whole !~ /\n\.include\s+"\$\{MASTERDIR\}\/Makefile"\s*$/s) { &perror("FATAL: $file: the last line of a slave port's Makefile has to be". ' .include "${MASTERDIR}/Makefile"'); } print "OK: checking master port in $masterdir.\n" if ($verbose); if (! -e "$masterdir/Makefile") { &perror("WARN: unable to locate master port in $masterdir"); } if ($whole !~ /^MASTERDIR=\s*\$\{\.CURDIR\}(?:\/\.\.){1,2}(?:\/[\w\@.+-]+){1,2}\s*$/m) { &perror("WARN: $file: slave ports must define MASTERDIR=". '${.CURDIR}/..(/../)/'); } } else { #$slaveport = 0; print "OK: non-slave port detected, checking for anything after bsd.port(.post).mk.\n" if ($verbose); if ($whole !~ /\n\.include\s+\s*$/s) { &perror("FATAL: $file: the last line of Makefile has to be". ' .include '); } if ($whole =~ /^MASTERDIR\s*[+?:!]?\s*=/m) { &perror("WARN: $file: non-slave ports may not define MASTERDIR"); } } # # break the makefile into sections. # $tmp = $rawwhole; # keep comment, blank line, comment in the same section $tmp =~ s/(#.*\n)\n+(#.*)/$1$2/g; @sections = split(/\n\n+/, $tmp); for ($i = 0; $i <= $#sections; $i++) { if ($sections[$i] !~ /\n$/) { $sections[$i] .= "\n"; } } $idx = 0; # # section 1: comment lines. # print "OK: checking comment section of $file.\n" if ($verbose); my @linestocheck = split("\n", <=! ])/) { &perror("FATAL: $file: PORTNAME contains the illegal character \"$1\".". " You should modify \"$portname\"."); } elsif ($portname =~ /\$[\{\(].+[\}\)]/) { &perror("WARN: $file: using variable in PORTNAME.". " consider using PKGNAMEPREFIX and/or PKGNAMESUFFIX."); } elsif ($portname =~ /([^\w._@+-])/) { &perror("WARN: $file: using \"$1\" in PORTNAME.". " You should modify \"$portname\"."); } elsif ($portname =~ /-/ && $distname ne '') { &perror("WARN: $file: using hyphen in PORTNAME.". " consider using PKGNAMEPREFIX and/or PKGNAMESUFFIX."); } if ($portversion eq '') { &perror("FATAL: $file: PORTVERSION must be specified"); } if ($portversion =~ /^pl[0-9]*$/ || $portversion =~ /^[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9+]*)*$/) { print "OK: PORTVERSION \"$portversion\" looks fine.\n" if ($verbose); } elsif ($portversion =~ /^[^\-]*\$[{\(].+[\)}][^\-]*$/) { &perror("WARN: $file: using variable, \"$portversion\", as version ". "number"); } elsif ($portversion =~ /([-,_<>=! #*])/) { &perror("FATAL: $file: PORTVERSION must not contain \"$1\". ". "You should modify \"$portversion\"."); } else { &perror("FATAL: $file: PORTVERSION looks illegal. ". "You should modify \"$portversion\"."); } $pkg_version = -x '/usr/local/sbin/pkg_version' ? '/usr/local/sbin/pkg_version' : '/usr/sbin/pkg_version'; $versiondir = $ENV{VERSIONDIR} ? $ENV{VERSIONDIR} : '/var/db/chkversion'; $versionfile = "$versiondir/VERSIONS"; $useindex = !-r "$versionfile"; $versionfile = "$portsdir/$makevar{INDEXFILE}" if $useindex; if (-r "$versionfile") { print "OK: checking if PORTVERSION is going backwards.\n" if ($verbose); open VERSIONS, "<$versionfile"; while () { my($origin, $version) = ('', ''); chomp; next if /^(#|$)/; if ($useindex) { ($version, $origin) = split /\|/; $origin =~ s,^.*/([^/]+/[^/]+)/?$,$1,; } else { ($origin, $version) = split; } if ($origin eq $makevar{PKGORIGIN}) { my $newversion = $makevar{PKGNAME}; my $oldversion = $version; my $result = ''; $newversion =~ s/^.*-//; $oldversion =~ s/^.*-//; $result = `$pkg_version -t '$newversion' '$oldversion'`; chomp $result; if ($result eq '<') { &perror("FATAL: $file: $makevar{PKGNAME} < $version. ". "Choose another PORTVERSION or bump PORTEPOCH."); # $backwards{$origin} = "$pkgname{$origin} < $version"; } last; } } close VERSIONS; } # if DISTFILES have only single item, it is better to avoid DISTFILES # and to use combination of DISTNAME and EXTRACT_SUFX. # example: # DISTFILES=package-1.0.tgz # should be # DISTNAME= package-1.0 # EXTRACT_SUFX= .tgz if ($distfiles =~ /^\S+$/ && $distfiles !~ /:[^\/:]+$/) { $bogusdistfiles++; print "OK: seen DISTFILES with single item, checking value.\n" if ($verbose); &perror("WARN: $file: use of DISTFILES with single file ". "discouraged. distribution filename should be set by ". "DISTNAME and EXTRACT_SUFX."); if ($distfiles eq (($distname ne '') ? $distname : "$portname-$portversion") . $extractsufx) { &perror("WARN: $file: definition of DISTFILES not necessary. ". "DISTFILES is \${DISTNAME}/\${EXTRACT_SUFX} ". "by default."); } # display advice only in certain cases. #MICHAEL: will this work with multiple distfiles in this list? what about # doing the same sort of thing for DISTNAME, is it needed? if ($distfiles =~ /^\Q$i\E([\-.].+)$/) { &perror("WARN: $file: how about \"EXTRACT_SUFX=$1\"". ", instead of DISTFILES?"); } } # additional checks for committer. if ($committer && $has_lang_cat) { &perror("WARN: $file: be sure to include language code ". "\"$port_lang-\" ". "in the module alias name."); } if ($committer) { if (opendir(DIR, ".")) { my @tgz = grep(/\.tgz$/, readdir(DIR)); closedir(DIR); if (@tgz) { my $tgz = (2 <= @tgz) ? '{' . join(',', @tgz) . '}' : $tgz[0]; &perror("WARN: be sure to remove $portdir/$tgz ". "before committing the port."); } } } push(@varnames, qw( PORTNAME PORTVERSION PORTREVISION PORTEPOCH CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR PKGNAMEPREFIX PKGNAMESUFFIX DISTNAME EXTRACT_SUFX DISTFILES EXTRACT_ONLY )); # # section 3: PATCH_SITES/PATCHFILES(optional) # print "OK: checking second section of $file (PATCH*: optional).\n" if ($verbose); $tmp = $sections[$idx]; if ($tmp =~ /(PATCH_SITES|PATCH_SITE_SUBDIR|PATCHFILES|PATCH_DIST_STRIP)/) { &checkearlier($file, $tmp, @varnames); if ($tmp =~ /^PATCH_SITES=/) { print "OK: seen PATCH_SITES.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCH_SITE_SUBDIR=/) { print "OK: seen PATCH_SITE_SUBDIR.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCHFILES=/) { print "OK: seen PATCHFILES.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCH_DIST_STRIP=/) { print "OK: seen PATCH_DIST_STRIP.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } &checkextra($tmp, 'PATCH_SITES', $file); $idx++; } push(@varnames, qw( PATCH_SITES PATCHFILES PATCH_DIST_STRIP )); # # section 4: MAINTAINER # print "OK: checking third section of $file (MAINTAINER).\n" if ($verbose); $tmp = $sections[$idx++]; &checkearlier($file, $tmp, @varnames); &checkorder('MAINTAINER', $tmp, $file, qw( MAINTAINER COMMENT )); $tmp = "\n" . $tmp; if ($tmp =~ /\nMAINTAINER\??=([^\n]+)/) { my $addr = $1; $addr =~ s/^\s*//; $addr =~ s/\s*$//; if ($addr =~ /[\s,<>()]/) { &perror("FATAL: $file: MAINTAINER should be a single address without comment."); } if ($addr !~ /^[^\@]+\@[\w\d\-\.]+$/) { &perror("FATAL: $file: MAINTAINER address, $addr, does not appear to be a valid email address."); } $tmp =~ s/\nMAINTAINER\??=[^\n]+//; } elsif ($whole !~ /\nMAINTAINER[?]?=/) { &perror("FATAL: $file: no MAINTAINER listed.") unless ($slaveport && $makevar{MAINTAINER} ne ''); } $tmp =~ s/\n\n+/\n/g; # check COMMENT if ($tmp !~ /\nCOMMENT(.)?=/) { &perror("FATAL: $file: COMMENT has to be there.") unless ($slaveport && $makevar{COMMENT} ne ''); } elsif ($1 ne '') { &perror("WARN: $file: unless this is a master port, COMMENT has to be set by \"=\", ". "not by \"$1=\".") unless ($masterport); } else { # check for correctness if (($makevar{COMMENT} !~ /^["0-9A-Z]/) || ($makevar{COMMENT} =~ m/\.$/)) { #" &perror("WARN: $file: COMMENT should begin with a capital, and end without a period"); } elsif (length($makevar{COMMENT}) > 70) { &perror("WARN: $file: COMMENT exceeds 70 characters limit."); } } push(@varnames, qw( MAINTAINER COMMENT )); # # section 5: *_DEPENDS (may not be there) # print "OK: checking fourth section of $file (*_DEPENDS).\n" if ($verbose); $tmp = $sections[$idx]; # NOTE: EXEC_DEPENDS is obsolete, so it should not be listed. @linestocheck = qw( EXTRACT_DEPENDS LIB_DEPENDS PATCH_DEPENDS BUILD_DEPENDS RUN_DEPENDS FETCH_DEPENDS DEPENDS DEPENDS_TARGET ); if ($tmp =~ /(LIB_|BUILD_|RUN_|FETCH_)?DEPENDS/) { &checkearlier($file, $tmp, @varnames); my %seen_depends; if (!defined $ENV{'PORTSDIR'}) { $ENV{'PORTSDIR'} = $portsdir; } foreach my $i (grep(/^[A-Z_]*DEPENDS[?+]?=/, split(/\n/, $tmp))) { $i =~ s/^([A-Z_]*DEPENDS)[?+]?=[ \t]*//; $j = $1; $seen_depends{$j}++; if ($j ne 'DEPENDS' && $i =~ /^\${([A-Z_]+DEPENDS)}\s*$/ && $seen_depends{$1} && $j ne $1) { print "OK: $j refers to $1, skipping checks.\n" if ($verbose); next; } print "OK: checking ports listed in $j.\n" if ($verbose); foreach my $k (split(/\s+/, $i)) { my @l = split(':', $k); print "OK: checking dependency value for $j.\n" if ($verbose); if (($j eq 'DEPENDS' && scalar(@l) != 1 && scalar(@l) != 2) || ($j ne 'DEPENDS' && scalar(@l) != 2 && scalar(@l) != 3)) { &perror("WARN: $file: wrong dependency value ". "for $j. $j requires ". ($j eq 'DEPENDS' ? "1 or 2 " : "2 or 3 "). "colon-separated tuples."); next; } my %m = (); if ($j eq 'DEPENDS') { $m{'dir'} = $l[0]; $m{'tgt'} = $l[1]; } else { $m{'dep'} = $l[0]; $m{'dir'} = $l[1]; $m{'tgt'} = $l[2]; } print "OK: dep=\"$m{'dep'}\", ". "dir=\"$m{'dir'}\", tgt=\"$m{'tgt'}\"\n" if ($verbose); # check USE_PERL5 if ($m{'dep'} =~ /^perl5(\.\d+)?$/) { &perror("WARN: $file: dependency to perl5 ". "listed in $j. consider using ". "USE_PERL5."); } # check USE_ICONV if ($m{'dep'} =~ /^(iconv\.\d+)$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_ICONV."); } # check USE_GETTEXT if ($m{'dep'} =~ /^(intl\.\d+)$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GETTEXT."); } # check USE_GMAKE if ($m{'dep'} =~ /^(gmake|\${GMAKE})$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GMAKE."); } # check USE_QT if ($m{'dep'} =~ /^(qt\d)+$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_QT."); } # check USE_GETOPT_LONG if ($m{'dep'} =~ /^(gnugetopt\.\d)+$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GETOPT_LONG."); + } + + # check LIBLTDL + if ($m{'dep'} =~ /^(ltdl\.\d)+$/) { + &perror("WARN: $file: dependency to $1 ". + "listed in $j. consider using ". + "USE_LIBLTDL."); } # check backslash in LIB_DEPENDS if ($osname eq 'NetBSD' && $j eq 'LIB_DEPENDS' && $m{'dep'} =~ /\\\\./) { &perror("WARN: $file: use of backslashes in ". "$j is deprecated."); } # check for PREFIX if ($m{'dep'} =~ /\${PREFIX}/) { &perror("FATAL: $file: \${PREFIX} must not be ". "contained in *_DEPENDS. ". "use \${LOCALBASE} or ". "\${X11BASE} instead."); } # check port dir existence $k = $m{'dir'}; $k =~ s/\${PORTSDIR}/$ENV{'PORTSDIR'}/; $k =~ s/\$[\({]PORTSDIR[\)}]/$ENV{'PORTSDIR'}/; if (! -d $k) { &perror("WARN: $file: no port directory $k ". "found, even though it is ". "listed in $j."); } else { print "OK: port directory $k found.\n" if ($verbose); } } } foreach my $i (@linestocheck) { $tmp =~ s/$i[?+]?=[^\n]+\n//g; } &checkextra($tmp, '*_DEPENDS', $file); $idx++; } push(@varnames, @linestocheck); &checkearlier($file, $tmp, @varnames); # # Makefile 6: check the rest of file # print "OK: checking the rest of the $file.\n" if ($verbose); $tmp = join("\n\n", @sections[$idx .. scalar(@sections)-1]); $tmp = "\n" . $tmp; # to make the begin-of-line check easier &checkearlier($file, $tmp, @varnames); # check WRKSRC/NO_WRKSUBDIR # # do not use DISTFILES/DISTNAME to control over WRKSRC. # DISTNAME is for controlling distribution filename. # example: # DISTNAME= package # DISTFILES=package-1.0.tgz # should be # DISTNAME= package-1.0 # EXTRACT_SUFX=.tgz # WRKSRC= ${WRKDIR}/package # print "OK: checking WRKSRC.\n" if ($verbose); $wrksrc = $nowrksubdir = ''; $wrksrc = $1 if ($tmp =~ /\nWRKSRC[+?]?=[ \t]*([^\n]*)\n/); $nowrksubdir = $1 if ($tmp =~ /\nNO_WRKSUBDIR[+?]?=[ \t]*([^\n]*)\n/); if ($nowrksubdir eq '') { $realwrksrc = $wrksrc ? "$wrksrc/$distname" : "\${WRKDIR}/$distname"; } else { $realwrksrc = $wrksrc ? $wrksrc : '${WRKDIR}'; } print "OK: WRKSRC seems to be $realwrksrc.\n" if ($verbose); if ($nowrksubdir eq '') { print "OK: no NO_WRKSUBDIR, checking value of WRKSRC.\n" if ($verbose); if ($wrksrc eq 'work' || $wrksrc =~ /^$[\{\(]WRKDIR[\}\)]/) { &perror("WARN: $file: WRKSRC is set to meaningless value ". "\"$1\".". ($nowrksubdir eq '' ? " use \"NO_WRKSUBDIR=yes\" instead." : "")); } if ($bogusdistfiles) { if ($distname ne '' && $wrksrc eq '') { &perror("WARN: $file: do not use DISTFILES and DISTNAME ". "to control WRKSRC. how about ". "\"WRKSRC=\${WRKDIR}/$distname\"?"); } else { &perror("WARN: $file: DISTFILES/DISTNAME affects WRKSRC. ". "take caution when changing them."); } } } else { print "OK: seen NO_WRKSUBDIR, checking value of WRKSRC.\n" if ($verbose); if ($wrksrc eq 'work' || $wrksrc =~ /^$[\{\(]WRKDIR[\}\)]/) { &perror("WARN: $file: definition of WRKSRC not necessary. ". "WRKSRC is \${WRKDIR} by default."); } } # check RESTRICTED/NO_CDROM/NO_PACKAGE print "OK: checking RESTRICTED/NO_CDROM/NO_PACKAGE.\n" if ($verbose); if ($committer && $tmp =~ /\n(RESTRICTED|NO_CDROM|NO_PACKAGE)[+?]?=/) { &perror("WARN: $file: \"$1\" found. do not forget to update ". "ports/LEGAL."); } # check NO_CONFIGURE/NO_PATCH print "OK: checking NO_CONFIGURE/NO_PATCH.\n" if ($verbose); if ($tmp =~ /\n(NO_CONFIGURE|NO_PATCH)[+?]?=/) { &perror("FATAL: $file: \"$1\" was obsoleted. remove this."); } # check MAN[1-9LN] print "OK: checking MAN[0-9LN].\n" if ($verbose); foreach my $i (keys %plistmanall) { print "OK: pkg-plist MAN$i=$plistmanall{$i}\n" if ($verbose); } foreach my $i (split(//, $manchapters)) { if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { print "OK: Makefile MAN\U$i\E=$1\n" if ($verbose); } } foreach my $i (split(//, $manchapters)) { next if ($i eq ''); if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { @mman = grep($_ !~ /^\s*$/, split(/\s+/, $1)); @pman = grep($_ !~ /^\s*$/, split(/\s+/, $plistmanall{$i})); foreach my $j (@mman) { print "OK: checking $j (Makefile)\n" if ($verbose); if ($automan && grep($_ eq $j, @pman)) { &perror("FATAL: $file: duplicated manpage ". "entry $j: content of ". "MAN\U$i\E will be automatically ". "added to pkg-plist."); } elsif (!$automan && !grep($_ eq $j, @pman)) { &perror("WARN: $file: manpage $j ". "MAN\U$i\E but not in pkg-plist."); } } foreach my $j (@pman) { print "OK: checking $j (pkg-plist)\n" if ($verbose); if (!grep($_ eq $j, @mman)) { &perror("WARN: $file: manpage $j in pkg-plist ". "but not in MAN\U$i\E."); } } } else { if ($plistmanall{$i}) { if ($manstrict) { &perror("FATAL: $file: manpage for chapter ". "$i must be listed in ". "MAN\U$i\E. "); } else { &perror("WARN: $file: manpage for chapter ". "$i should be listed in ". "MAN\U$i\E, ". "even if compression is ". "not necessary."); } } if ($mancompress && $plistman{$i}) { &perror("WARN: $file: MAN\U$i\E will help you ". "compressing manual page in chapter ". "\"$i\"."); } elsif (!$mancompress && $plistmangz{$i}) { &perror("WARN: $file: MAN\U$i\E will help you ". "uncompressing manual page in chapter ". "\"$i\"."); } } } if ($tmp !~ /MANLANG/ && scalar(keys %manlangs)) { $i = (keys %manlangs)[0]; &perror("WARN: $file: how about using MANLANG for ". "designating manual language, such as \"$i\"?"); } # check INFO print "OK: checking INFO.\n" if ($verbose); if ($autoinfo && $tmp =~ /\nINFO=\s*([^\n]*)\n/) { my @minfo = grep($_ !~ /^\s*$/, split(/\s+/, $1)); foreach $i (@minfo) { if ($i =~ /\.info(-\d+)?$/) { &perror("FATAL: $file: do not include the .info extension ". "on files listed in the INFO macro."); } } } # check USE_X11 and USE_IMAKE if ($tmp =~ /\nUSE_IMAKE[?+]?=/ && $tmp =~ /\n(USE_X11)[?+]?=/) { &perror("WARN: $file: since you already have USE_IMAKE, ". "you don't need $1."); } # check USE_X11 and USE_IMAKE if ($newxdef && $tmp =~ /\nUSE_IMAKE[?+]?=/ && $tmp =~ /\n(USE_X_PREFIX)[?+]?=/) { &perror("WARN: $file: since you already have USE_IMAKE, ". "you don't need $1."); } # check USE_X11 and USE_X_PREFIX if ($newxdef && $tmp =~ /\nUSE_X11[?+]?=/ && $tmp !~ /\nUSE_X_PREFIX[?+]?=/) { &perror("FATAL: $file: meaning of USE_X11 was changed in Aug 1998. ". "use USE_X_PREFIX instead."); } # check direct use of important make targets. if ($tmp =~ /\n(fetch|extract|patch|configure|build|install):/) { &perror("FATAL: $file: direct redefinition of make target \"$1\" ". "discouraged. redefine \"do-$1\" instead."); } # check for incorrect use of the pre-everything target. if ($tmp =~ /\npre-everything:[^:]/) { &perror("FATAL: $file: use pre-everything:: instead of pre-everything:"); } if ($tmp =~ /^pre-patch:/m && $use_gnome_hack) { &perror("FATAL: $file: pre-patch target overwrites gnomehack component. ". "use post-patch instead."); } 1; } sub perror { my(@msg) = @_; if ($msg[0] =~ /^FATAL/) { $err++; } else { $warn++; } print join("\n", @msg) . "\n"; } sub checkextra { my($str, $section, $file) = @_; $str = "\n" . $str if ($str !~ /^\n/); $str =~ s/\n#[^\n]*/\n/g; $str =~ s/\n\.[^\n]+/\n/g; $str =~ s/\n\n+/\n/g; $str =~ s/^\s+//; $str =~ s/\s+$//; return if ($str eq ''); if ($str =~ /^([\w\d]+)/) { &perror("WARN: $file: extra item placed in the ". "$section section, ". "for example, \"$1\"."); } else { &perror("WARN: $file: extra item placed in the ". "$section section."); } } sub checkorder { my($section, $str, $file, @order) = @_; my(@items, $i, $j, $k, $invalidorder); print "OK: checking the order of $section section.\n" if ($verbose); @items = (); foreach my $i (split("\n", $str)) { $i =~ s/[+?]?=.*$//; push(@items, $i); } @items = reverse(@items); $j = -1; $invalidorder = 0; while (scalar(@items)) { $i = pop(@items); $k = 0; while ($k < scalar(@order) && $order[$k] ne $i) { $k++; } if ($order[$k] eq $i) { if ($k < $j) { &perror("FATAL: $file: $i appears out-of-order."); $invalidorder++; } else { print "OK: seen $i, in order.\n" if ($verbose); } $j = $k; # This if condition tests for .if, .else (in all forms), # .for and .endfor and .include } elsif ($i !~ m/^\.(if|el|endif|for|endfor|include)/) { &perror("FATAL: $file: extra item \"$i\" placed in the ". "$section section."); } } if ($invalidorder) { &perror("FATAL: $file: order must be " . join('/', @order) . '.'); } else { print "OK: $section section is ordered properly.\n" if ($verbose); } } sub checkearlier { my($file, $str, @varnames) = @_; my($i); print "OK: checking items that has to appear earlier.\n" if ($verbose); foreach my $i (@varnames) { if ($str =~ /\n$i\??=/) { &perror("WARN: $file: \"$i\" has to appear earlier."); } } } sub linenumber { my $text = shift; my @lines; @lines = split /\n/, $text; return scalar(@lines); } sub abspathname { my($str, $file) = @_; my($s, $i, %cmdnames); my($pre); # trim all trailing backslash and newline $str =~ s/\\\n\s*/ /g; # ignore parameter string to reinplace command $str =~ s/([ \t][\@-]?(?:sed|\$[\{\(]SED[\}\)]|\$[\{\(]REINPLACE_CMD[\}\)]))((?:\s+\-\w+)*\s+(?:"(?:\\"|[^"\n])*"|'(?:\\'|[^'\n])*'))+(.*)/$1$3/g; #' # ignore parameter string to echo command $str =~ s/[ \t][\@-]?(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+("(\\'|\\"|[^"])*"|'(\\'|\\"|[^"])*')[ \t]*[;\n]//; #' print "OK: checking direct use of full pathnames in $file.\n" if ($verbose); foreach my $s (split(/\n+/, $str)) { $i = ''; if ($s =~ /(^|[ \t\@'"-])(\/[\w\d])/) { #' # suspected pathnames are recorded. $i = $2 . $'; $pre = $` . $1; if ($pre =~ /MASTER_SITE_SUBDIR/) { # MASTER_SITE_SUBDIR lines are ok. $i = ''; } } if ($i ne '') { $i =~ s/\s.*$//; $i =~ s/['"].*$//; #' $i = substr($i, 0, 20) . '...' if (20 < length($i)); &perror("WARN: $file: possible use of absolute pathname ". "\"$i\".") unless ($i =~ m,^/compat/,); } } print "OK: checking direct use of pathnames, phase 1.\n" if ($verbose); %cmdnames = split(/\n|\t+/, < # # $FreeBSD$ # # This port is self contained in the src directory. # PORTNAME= portlint -PORTVERSION= 2.6.2 +PORTVERSION= 2.6.3 CATEGORIES= devel MASTER_SITES= # none DISTFILES= # none MAINTAINER= marcus@FreeBSD.org COMMENT= A verifier for FreeBSD port directory NO_BUILD= yes WRKSRC= ${WRKDIR}/src USE_PERL5= yes USE_REINPLACE= yes SRC= ${.CURDIR}/src MAN1= portlint.1 do-fetch: @${DO_NADA} pre-patch: @${CP} -R ${SRC} ${WRKDIR} post-patch: @${REINPLACE_CMD} -e 's|/usr/bin/perl|${PERL}|' ${WRKSRC}/portlint.pl @${REINPLACE_CMD} -e 's|/usr/bin/perl|${PERL}|' \ ${WRKSRC}/portlintgrep.pl do-install: ${INSTALL_SCRIPT} ${WRKSRC}/portlint.pl ${PREFIX}/bin/portlint ${INSTALL_MAN} ${WRKSRC}/portlint.1 ${MAN1PREFIX}/man/man1 ${MKDIR} ${EXAMPLESDIR} ${INSTALL_SCRIPT} ${WRKSRC}/portlintgrep.pl ${EXAMPLESDIR}/portlintgrep .include Property changes on: head/ports-mgmt/portlint/Makefile ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.77 \ No newline at end of property +1.78 \ No newline at end of property Index: head/ports-mgmt/portlint/src/portlint.pl =================================================================== --- head/ports-mgmt/portlint/src/portlint.pl (revision 110913) +++ head/ports-mgmt/portlint/src/portlint.pl (revision 110914) @@ -1,2411 +1,2443 @@ #! /usr/bin/perl # ex:ts=4 # # portlint - lint for port directory # implemented by: # Jun-ichiro itojun Hagino # Yoshishige Arai # # Copyright(c) 1997 by Jun-ichiro Hagino . # All rights reserved. # Freely redistributable. Absolutely no warranty. # # Please note that this perl code used to be able to handle (Open|Net|Free)BSD # bsd.port.mk. There are significant differences in those so you'll have # hard time upgrading this... # This code now mainly supports FreeBSD, but patches to update support for # OpenBSD and NetBSD will be accepted. # # $FreeBSD$ -# $Id: portlint.pl,v 1.47 2004/05/30 19:54:47 marcus Exp $ +# $Id: portlint.pl,v 1.48 2004/06/06 01:04:42 marcus Exp $ # use vars qw/ $opt_a $opt_A $opt_b $opt_c $opt_h $opt_t $opt_v $opt_M $opt_N $opt_B $opt_V /; use Getopt::Std; use File::Find; use IPC::Open2; #use strict; my ($err, $warn); my ($extrafile, $parenwarn, $committer, $verbose, $usetabs, $newport); my $contblank; my $portdir; my $makeenv = ""; $err = $warn = 0; $extrafile = $parenwarn = $committer = $verbose = $usetabs = $newport = 0; $contblank = 1; $portdir = '.'; # version variables my $major = 2; my $minor = 6; -my $micro = 2; +my $micro = 3; sub l { '[{(]'; } sub r { '[)}]'; } sub s { '[ \t]'; } my $l = &l; my $r = &r; my $s = &s; # default setting - for FreeBSD my $portsdir = '/usr/ports'; my $rcsidstr = 'FreeBSD'; my $multiplist = 0; my $ldconfigwithtrue = 0; my $rcsidinplist = 0; my $mancompress = 1; my $manstrict = 0; my $newxdef = 1; my $automan = 1; my $autoinfo = 1; my $use_no_size = 0; +my $use_no_checksum = 0; my $manchapters = '123456789ln'; my $localbase = '/usr/local'; my %lang_pref = qw( arabic ar chinese zh french fr german de hebrew iw hungarian hu japanese ja korean ko polish pl portuguese pt russian ru ukrainian uk vietnamese vi ); my @lang_cat = keys %lang_pref; my @lang_short = values %lang_pref; my $re_lang_short = '(' . join('|', @lang_short) . ')-'; my ($prog) = ($0 =~ /([^\/]+)$/); sub usage { print STDERR <); close(MK); $cmd = join(' -V MASTER_SITE_', "make $makeenv -f - all", @site_groups); $i = 0; open2(IN, OUT, $cmd); print OUT <) { my $g = $site_groups[$i]; for my $s (split()) { $predefined{$s} = $g; } $i++; } close(IN); # # check for files. # my @checker = ($makevar{DESCR}, 'Makefile', $makevar{MD5_FILE}); my %checker = ( $makevar{DESCR} => 'checkdescr', 'Makefile' => 'checkmakefile', $makevar{MD5_FILE} => 'checkdistinfo', ); if ($extrafile) { my @files = ( <$makevar{SCRIPTDIR}/*>, @makevar{DESCR,PLIST,PKGINSTALL,PKGDEINSTALL,PKGREQ,PKGMESSAGE} ); foreach my $i (@files) { next if (! -T $i); next if (defined $checker{$i}); if ($i =~ /\bpkg-plist$/ || ($multiplist && $i =~ /\bpkg-plist/)) { unshift(@checker, $i); $checker{$i} = 'checkplist'; } else { push(@checker, $i); $checker{$i} = 'checkpathname'; } } } foreach my $i (<$makevar{PATCHDIR}/patch-*>) { next if (! -T $i); next if (defined $checker{$i}); push(@checker, $i); $checker{$i} = 'checkpatch'; } foreach my $i (@checker) { print "OK: checking $i.\n" if ($verbose); if (! -f "$i") { &perror("FATAL: no $i in \"$portdir\".") unless $i eq $makevar{MD5_FILE} && $makevar{DISTFILES} eq ""; } else { my $proc = $checker{$i}; &$proc($i) || &perror("Cannot open the file $i\n"); if ($proc ne 'checkpatch') { &checklastline($i) || &perror("Cannot open the file $i\n"); } } } # Check to make sure there is no pkg-comment file anymore. if (-f 'pkg-comment') { &perror("FATAL: Use of pkg-comment is obsolete. Use the COMMENT macro within the port's Makefile instead."); } if ($committer) { sub find_proc { return if /^\.\.?$/; (my $fullname = $File::Find::name) =~ s#^\./##; print "OK: checking the file name of $fullname.\n" if ($verbose); if ($fullname eq 'work') { &perror("FATAL: $fullname: be sure to cleanup the working directory ". "before committing the port."); $File::Find::prune = 1; } elsif (-l) { &perror("Warning: $fullname: this is a symlink. ". "CVS will ignore it."); } elsif (-z) { &perror("FATAL: $fullname: empty file and should be removed. ". "If it still needs to be there, put a dummy comment ". "to state that the file is intentionally left empty."); } elsif (-d && scalar(my @x = <$_/{*,.?*}>) <= 1) { &perror("FATAL: $fullname: empty directory should be removed."); } elsif (/^\./) { &perror("Warning: $fullname: dotfiles are not preferred. ". "If this file is a dotfile to be installed as an example, ". "consider importing it as \"dot$_\"."); } elsif (/\.(orig|rej|bak)$/ || /~$/ || /^\#/) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "backup files before committing the port."); } elsif (/(^|\.)core$/) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "core files before committing the port."); } elsif ($_ eq 'CVS' && -d) { if ($newport) { &perror("FATAL: $fullname: for safety, be sure to cleanup ". "CVS directories before importing the new port."); } $File::Find::prune = 1; } } find(\&find_proc, '.'); sub checksubdir { my $dir = shift; print "OK: checking CVS status of \"$dir\".\n" if ($verbose); opendir DIR, $dir; my @filenames = readdir DIR; closedir DIR; my %entries; if (-f "$dir/CVS/Entries") { open ENTRIES, "<$dir/CVS/Entries"; while () { chomp; my @entry = split /\//; if ($entry[0] eq 'D') { $entries{ $entry[1] } = $entry[0] if $entry[1]; } elsif ($entry[0] eq '') { if ($entry[2] =~ /^-/) { $entries{ $entry[1] } = 'x'; } elsif ($entry[2] eq '0') { $entries{ $entry[1] } = 'n'; } else { $entries{ $entry[1] } = 'f'; } } else { &perror("WARN: can not parse CVS line $_"); } } close ENTRIES; } else { &perror("WARN: no CVS directories. Use -N to check a new port."); return; } if (-f "$dir/CVS/Entries.Log") { open ENTRIES, "<$dir/CVS/Entries.Log"; while () { chomp; my $cmd; my @entry = split /\//; if (/^(.) (.*)$/) { $cmd = $1; @entry = split /\//, $2; } else { $cmd = 'A'; @entry = split /\//; } if ($cmd eq 'A') { if ($entry[0] eq 'D') { $entries{ $entry[1] } = $entry[0] if $entry[1]; } elsif ($entry[0] eq '') { if ($entry[2] =~ /^-/) { $entries{ $entry[1] } = 'x'; } elsif ($entry[2] eq '0') { $entries{ $entry[1] } = 'n'; } else { $entries{ $entry[1] } = 'f'; } } else { &perror("WARN: can not parse CVS line $_"); } } elsif ($cmd eq 'R') { delete $entries{ $entry[1] } if $entry[1]; } # ignore unknown commands } close ENTRIES; } foreach (@filenames) { next if /^(?:\.\.?|CVS)$/; my $filename = $dir eq '.' ? $_ : "$dir/$_"; if (-d $filename) { if (!$entries{$_} || $entries{$_} ne 'D') { &perror("FATAL: directory $filename not in CVS."); } else { delete $entries{$_}; checksubdir($filename); } } else { if (!$entries{$_}) { &perror("FATAL: file $filename not in CVS.") unless (eval { /$ENV{'PL_CVS_IGNORE'}/, 1 } && /$ENV{'PL_CVS_IGNORE'}/); } elsif ($entries{$_} eq 'D') { &perror("FATAL: file $filename is a directory in CVS."); } elsif ($entries{$_} eq 'x') { &perror("FATAL: file $filename is deleted in CVS."); } elsif ($entries{$_} eq 'n') { if (!system("egrep", "-q", "\\\$$rcsidstr\[^\$\]+\\\$", $filename)) { &perror("WARN: RCS tag \"\$$rcsidstr\$\" ". "should be empty in new file $filename."); } delete $entries{$_}; } else { delete $entries{$_}; } } } while (my ($file, $type) = each %entries) { next if $type eq 'x'; if ($type eq 'D') { &perror("FATAL: CVS directory $dir/$file missing"); } else { &perror("FATAL: CVS file $dir/$file missing"); } } } checksubdir('.') unless $newport; # Check for ports that may break INDEX my $indexerr = `env LOCALBASE=/nonexistentlocal X11BASE=/nonexistentx make $makeenv describe 2>&1 >/dev/null`; chomp $indexerr; $indexerr =~ tr/\n/ /s; &perror("FATAL: breaks INDEX ($indexerr).") if ($indexerr); } if ($err || $warn) { print "$err fatal errors and $warn warnings found.\n" } else { print "looks fine.\n"; } exit $err; # # distinfo # sub checkdistinfo { my($file) = @_; my($sizefound) = 0; + my(@distinfo) = (); + my(@distfiles) = (); + open(IN, "< $file") || return 0; - while () { - if ($_ =~ /^SIZE/) { - $sizefound = 1; - } - } - if (!$sizefound && !$use_no_size) { - &perror("WARN: $file: does not contain SIZE."); - } + @distinfo = ; close(IN); + + @distfiles = split(/\s+/, $makevar{DISTFILES}); + + foreach my $distfile (@distfiles) { + if (!(grep /^SIZE \(([^\)]*\/)?$distfile\)/, @distinfo) && + !$use_no_size) { + &perror("WARN: $file: no SIZE entry found for $distfile."); + } + if (!(grep /^MD5 \(([^\)]*\/)?$distfile\)/, @distinfo) && + !$use_no_checksum) { + &perror("WARN: $file: no MD5 entry found for $distfile."); + } + } + + 1; } # # pkg-descr # sub checkdescr { my($file) = @_; my(%maxchars) = ($makevar{DESCR}, 80); my(%maxlines) = ($makevar{DESCR}, 24); my(%errmsg) = ($makevar{DESCR}, "exceeds $maxlines{$makevar{DESCR}} ". "lines, make it shorter if possible."); my($longlines, $linecnt, $tmp) = (0, 0, ""); open(IN, "< $file") || return 0; while () { $tmp .= $_; chomp || &perror("WARN: $file: should terminate in '\n'."); $linecnt++; $longlines++ if ($maxchars{$file} < length); } if ($linecnt > $maxlines{$file}) { &perror("WARN: $file $errmsg{$file}". "(currently $linecnt lines)"); } else { print "OK: $file: has $linecnt lines.\n" if ($verbose); } if ($longlines > 0) { &perror("WARN: $file: includes lines that exceed $maxchars{$file} ". "characters."); } if ($tmp =~ /[\033\200-\377]/) { &perror("WARN: $file: includes iso-8859-1, or ". "other local characters. files should be in ". "plain 7-bit ASCII"); } if ($file =~ /\bpkg-descr/ && $tmp =~ m,http://,) { my $has_url = 0; my $has_www = 0; foreach my $line (grep($_ =~ "http://", split(/\n+/, $tmp))) { $has_url = 1; if ($line =~ m,WWW:[ \t]+http://,) { $has_www = 1; } } if ($has_url && ! $has_www) { &perror("FATAL: $file: contains a URL but no \"WWW:\""); } } close(IN); } # # pkg-plist # sub checkplist { my($file) = @_; my($curdir) = ($localbase); my(%omfremoveseen) = (); my(%omfinstallseen) = (); my(%omfseen) = (); my($inforemoveseen, $infoinstallseen, $infoseen) = (0, 0, 0); my(%omfafterinstall) = (); my(%omfafterremove) = (); my($infobeforeremove, $infoafterinstall) = (0, 0); my($infooverwrite) = (0); my($rcsidseen) = (0); my(@exec_omf) = (); my(@exec_info) = (); my(@unexec_omf) = (); my(@unexec_info) = (); my(@omffile) = (); my(@infofile) = (); my $seen_dirrm_docsdir; open(IN, "< $file") || return 0; while () { if ($_ =~ /[ \t]+\n?$/) { &perror("WARN: $file [$.]: whitespace before end ". "of line."); } # make it easier to handle. $_ =~ s/\s+$//; $_ =~ s/\n$//; if ($osname eq 'NetBSD' && $_ =~ /<\$ARCH>/) { &perror("WARN: $file [$.]: use of <\$ARCH> deprecated, ". "use \${MACHINE_ARCH} instead."); } if (m'lib/perl5/site_perl/%%PERL_VER%%') { &perror("WARN: $file [$.]: use \%\%SITE_PERL\%\% ". "instead of lib/perl5/site_perl/\%\%PERL_VER\%\%."); } $seen_dirrm_docsdir++ if /^(\%\%PORTDOCS\%\%)?\@dirrm\s+\%\%DOCSDIR\%\%/ || /^(\%\%PORTDOCS\%\%)?\@unexec\s+(\/bin\/)?rmdir\s+\%D\/\%\%DOCSDIR\%\%\s+2\>\s*\/dev\/null\s+\|\|\s+(\/usr\/bin\/)?true/; if ($_ =~ /^\@/) { if ($_ =~ /^\@(cwd|cd)[ \t]+(\S+)/) { $curdir = $2; } elsif ($_ =~ /^\@unexec[ \t]+rm[ \t]/) { if ($_ !~ /%[DB]/) { &perror("WARN: $file [$.]: use \"%D\" or \"%B\" to ". "specify prefix."); } if ($_ !~ /true$/ && $_ !~ /rm -f/) { &perror("WARN: $file [$.]: add \"2>&1 ". ">/dev/null || true\" ". "to \"\@unexec rm\"."); } } elsif ($_ =~ /^\@unexec[ \t]+rmdir/) { if ($_ !~ /%[DB]/) { &perror("WARN: $file [$.]: use \"%D\" or \"%B\" to ". "specify prefix."); } if ($_ !~ /true$/) { &perror("WARN: $file [$.]: use \"\@dirrm\" ". "instead of \"\@unexec rmdir\"."); } } elsif ($_ =~ /^\@exec[ \t]+scrollkeeper-install[ \t]+-q\s+(\S+)\s+.+$/) { push(@exec_omf, $1); my $ot = $1; $ot =~ s/^\%D\///; $omfinstallseen{$ot} = $.; } elsif (!$autoinfo && $_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { $infoinstallseen = $.; push(@exec_info, $1); } elsif ($autoinfo && $_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { &perror("WARN: $file [$.]: \@exec install-info is deprecated in favor of adding info files into the Makefile using the INFO macro."); } elsif ($_ =~ /^\@unexec[ \t]+scrollkeeper-uninstall[ \t]+-q\s+(\S+)\s+.+$/) { push(@unexec_omf, $1); my $ot = $1; $ot =~ s/^\%D\///; $omfremoveseen{$ot} = $.; } elsif (!$autoinfo && $_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { $inforemoveseen = $.; push(@unexec_info, $1); } elsif ($autoinfo && $_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { &perror("WARN: $file [$.]: \@unexec install-info is deprecated in favor of adding info files into the Makefile using the INFO macro."); } elsif ($_ =~ /^\@(exec|unexec)/) { if (/ldconfig/) { if ($ldconfigwithtrue && !/\/usr\/bin\/true/) { &perror("FATAL: $file [$.]: ldconfig ". "must be used with ". "\"||/usr/bin/true\"."); } &perror("WARN: $file [$.]: possible ". "direct use of ldconfig ". "in PLIST found. use ". "INSTALLS_SHLIB instead."); } } elsif ($_ =~ /^\@(comment)/) { $rcsidseen++ if (/\$$rcsidstr[:\$]/); } elsif ($_ =~ /^\@(owner|group)\s/) { &perror("WARN: $file [$.]: \@$1 should not be needed"); } elsif ($_ =~ /^\@(dirrm|option)/) { ; # no check made } else { &perror("WARN: $file [$.]: ". "unknown pkg-plist directive \"$_\""); } next; } if ($_ =~ /^\//) { &perror("FATAL: $file [$.]: use of full pathname ". "disallowed."); } - if ($_ =~ /\.la$/ && $makevar{USE_LIBTOOL} eq '' && - $makevar{USE_LIBTOOL_VER} eq '') { - &perror("WARN: $file [$.]: installing libtool archives, ". + if ($_ =~ /\.la$/ && $makevar{USE_LIBTOOL_VER} eq '') { + &perror("WARN: $file [$.]: installing libtool archives, ". "please use USE_LIBTOOL_VER in Makefile if possible. ". "See http://www.FreeBSD.org/gnome/docs/portlint.html ". "for a way to completely eliminate .la files."); } if ($_ =~ m|^lib/lib[^\/]+\.so(\.\d+)?$| && $makevar{INSTALLS_SHLIB} eq '') { &perror("WARN: $file [$.]: installing shared libraries, ". "please define INSTALLS_SHLIB as appropriate"); } if ($autoinfo && $_ =~ /\.info$/) { &perror("WARN: $file [$.]: enumerating info files in the plist is deprecated in favor of adding info files into the Makefile using the INFO macro."); } if ($autoinfo && $_ =~ /\.info-\d+$/) { &perror("FATAL: $file [$.]: numbered info files are obsolete and not portable; add info files using the INFO macro in the Makefile."); } if ($_ =~ /.*\.omf$/) { $omfseen{$_} = $.; $omfafterinstall{$_}++ if ($omfinstallseen{$_}); $omfafterremove{$_}++ if ($omfremoveseen{$_}); push(@omffile, $_); } if (!$autoinfo) { if ($_ =~ /^info\/.*info(-[0-9]+)?$/) { $infoseen = $.; $infoafterinstall++ if ($infoinstallseen); $infobeforeremove++ if (!$inforemoveseen); push(@infofile, $_); } if ($_ =~ /^info\/dir$/) { &perror("FATAL: $file [$.]: \"info/dir\" should not be listed.". "Use install-info to add/remove ". "an entry."); $infooverwrite++; } } if ($_ =~ /^(\%\%PORTDOCS\%\%)?share\/doc\//) { &perror("WARN: $file [$.]: consider using DOCSDIR macro"); $sharedocused++; } elsif ($_ =~ /^(\%\%PORTDOCS\%\%)?\%\%DOCSDIR\%\%/) { $sharedocused++; } if ($_ =~ /^share\/examples\//) { &perror("WARN: $file [$.]: consider using EXAMPLESDIR macro"); } if ($_ =~ m#man/([^/]+/)?man([$manchapters])/([^\.]+\.[$manchapters])(\.gz)?$#) { if ($4 eq '') { $plistman{$2} .= ' ' . $3; if ($mancompress) { &perror("FATAL: $file [$.]: ". "unpacked man file $3 ". "listed. must be gzipped."); } } else { $plistmangz{$2} .= ' ' . $3; if (!$mancompress) { &perror("FATAL: $file [$.]: ". "gzipped man file $3$4 ". "listed. unpacked one should ". "be installed."); } } $plistmanall{$2} .= ' ' . $3; if ($1 ne '') { $manlangs{substr($1, 0, length($1) - 1)}++; } } if ($curdir !~ m#^$localbase# && $curdir !~ m#^/usr/X11R6#) { &perror("WARN: $file [$.]: installing to ". "directory $curdir discouraged. ". "could you please avoid it?"); } if ("$curdir/$_" =~ m#^$localbase/share/doc#) { print "OK: $file [$.]: seen installation to share/doc. ". "($curdir/$_)\n" if ($verbose); $sharedocused++; } } if ($sharedocused && !$seen_dirrm_docsdir) { &perror("WARN: $file: Both ``\%\%PORTDOCS\%\%\@dirrm \%\%DOCSDIR\%\%'' and ``\%\%PORTDOCS\%\%\@unexec \%D/\%\%DOCSDIR\%\% 2>/dev/null || true'' are missing. At least one should be used."); } # Check that each OMF file has an install and deinstall line. my $omf_install = join(" ", @exec_omf); $omf_install .= ' '; my $omf_deinstall = join(" ", @unexec_omf); $omf_deinstall .= ' '; foreach my $of (@omffile) { if ($omf_install !~ /\%D\/\Q$of\E/) { &perror("FATAL: $file: you need an '\@exec scrollkeeper-install -q \%D/$of 2>/dev/null || /usr/bin/true' line"); } if ($omf_deinstall !~ /\%D\/$of/) { &perror("FATAL: $file: you need an '\@unexec scrollkeeper-uninstall -q \%D/$of 2>/dev/null || /usr/bin/true' line"); } } if (!$autoinfo) { # check that every infofile has an exec install-info and unexec install-info my $exec_install = join(" ", @exec_info); $exec_install .= ' '; my $unexec_install = join(" ", @unexec_info); $unexec_install .= ' '; foreach my $if (@infofile) { next if ($if =~ m/info-/); if ($exec_install !~ m/\%D\/\Q$if\E/) { &perror("FATAL: $file: you need an '\@exec install-info \%D/$if \%D/info/dir' line"); } if ($unexec_install !~ m/\%D\/$if/) { &perror("FATAL: $file: you need an '\@unexec install-info --delete \%D/$if \%D/info/dir' line"); } } } if ($rcsidinplist && !$rcsidseen) { &perror("FATAL: $file: RCS tag \"\$$rcsidstr\$\" must be present ". "as \@comment.") } if (((!$autoinfo && !$infoseen) || $autoinfo) && !scalar(keys %omfseen)) { close(IN); return 1; } if (scalar(keys %omfseen)) { if (!scalar(keys %omfinstallseen)) { &perror("FATAL: $file: scrollkeeper-install must be used to ". "add/delete entries from the ScrollKeeper OMF database."); } else { foreach my $of (keys %omfseen) { if ($omfafterinstall{$of}) { &perror("FATAL: $file [$omfinstallseen{$of}]: move ". "\"\@exec scrollkeeper-install\" ". "line to make sure that it is placed after ". "the $of entry."); } } } if (!scalar(keys %omfremoveseen)) { &perror("FATAL: $file: \"\@unexec scrollkeeper-uninstall\" must ". "be placed after the OMF file it uninstalls."); } else { foreach my $of (keys %omfseen) { if ($omfafterremove{$of}) { &perror("FATAL: $file [$omfremoveseen{$of}]: move ". "\"\@unexec scrollkeeper-uninstall\" ". "line to make sure that it is placed after ". "the $of entry."); } } } } if (!$autoinfo && $infoseen) { if (!$infoinstallseen) { if ($infooverwrite) { &perror("FATAL: $file: install-info must be used to ". "add/delete entries into \"info/dir\"."); } &perror("FATAL: $file: \"\@exec install-info \%D/... \%D/info/dir\" must be placed ". "after all the info files."); } elsif ($infoafterinstall) { &perror("FATAL: $file [$infoinstallseen]: move ". "\"\@exec install-info\" line to make ". "sure that it is placed after all the info files."); } if (!$inforemoveseen) { &perror("FATAL: $file: \"\@unexec install-info --delete \%D/... \%D/info/dir\" must ". "be placed before any of the info files listed."); } elsif ($infobeforeremove) { &perror("FATAL: $file [$inforemoveseen]: move ". "\"\@exec install-info --delete\" ". "line to make sure ". "that it is placed before any of the info files. "); } } close(IN); } # # misc files # sub checkpathname { my($file) = @_; my($whole); open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } &abspathname($whole, $file); close(IN); } sub checklastline { my($file) = @_; my($whole); open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } if ($whole !~ /\n$/) { &perror("FATAL: $file: the last line has to be ". "terminated by \\n."); } if ($whole =~ /\n([ \t]*\n)+$/) { &perror("WARN: $file: seems to have unnecessary blank lines ". "at the last part."); } close(IN); } sub checkpatch { my($file) = @_; my($whole); if (-z "$file") { &perror("FATAL: $file: has no content. should be removed ". "from repository."); return; } open(IN, "< $file") || return 0; $whole = ''; while () { $whole .= $_; } if ($committer && $whole =~ /\$([A-Z][A-Za-z0-9]+)(:[^\n]+)?\$/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: includes possible RCS tag \"\$$1\$\". ". "use binary mode (-ko) on commit/import.") unless $1 eq $rcsidstr; } close(IN); } # # Makefile # sub checkmakefile { my($file) = @_; my($rawwhole, $whole, $idx, @sections); my($i, $j, $k, $l); my @cat = (); my $has_lang_cat = 0; my $port_lang = ''; my $tmp; my $bogusdistfiles = 0; my @varnames = (); my($portname, $portversion, $distfiles, $distname, $extractsufx) = ('', '', '', '', ''); my $masterport = 0; my $slaveport = 0; my $use_gnome_hack = 0; my($realwrksrc, $wrksrc, $nowrksubdir) = ('', '', ''); my(@mman, @pman); my($pkg_version, $versiondir, $versionfile) = ('', '', ''); my $useindex = 0; + my %deprecated = (); open(IN, "< $file") || return 0; $rawwhole = ''; $tmp = 0; while () { if ($_ =~ /[ \t]+\n?$/) { &perror("WARN: $file [$.]: whitespace before ". "end of line."); } if ($_ =~ /^ /) { # 8 spaces here! &perror("WARN: $file [$.]: use tab (not space) to make ". "indentation"); } if ($usetabs) { if (m/^[A-Za-z0-9_-]+.?= /) { if (m/[?+]=/) { &perror("WARN: $file [$.]: use a tab (not space) after a ". "variable name"); } else { &perror("FATAL: $file [$.]: use a tab (not space) after a ". "variable name"); } } } # # I'm still not very convinced, for using this kind of magical word. # 1. This kind of items are not important for Makefile; # portlint should not require any additional rule to Makefile. # portlint should simply implement items that are declared in Handbook. # 2. If we have LINTSKIP, we can't stop people using LINTSKIP too much. # IMHO it is better to warn the user and let the user think twice, # than let the user escape from portlint. # Uncomment this part if you are willing to use these magical words. # Thu Jun 26 11:37:56 JST 1997 # -- itojun # # if ($_ =~ /^# LINTSKIP\n?$/) { # print "OK: skipping from line $. in $file.\n" # if ($verbose); # $tmp = 1; # next; # } # if ($_ =~ /^# LINTAGAIN\n?$/) { # print "OK: check start again from line $. in $file.\n" # if ($verbose); # $tmp = 0; # next; # } # if ($_ =~ /# LINTIGNORE/) { # print "OK: ignoring line $. in $file.\n" if ($verbose); # next; # } # next if ($tmp); $rawwhole .= $_; } close(IN); # # whole file: blank lines. # $whole = "\n" . $rawwhole; study $whole; print "OK: checking contiguous blank lines in $file.\n" if ($verbose); $i = "\n" x ($contblank + 2); if ($whole =~ /$i/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: contiguous blank lines ". "(> $contblank lines) found."); } # # whole file: $(VARIABLE) # if ($parenwarn) { print "OK: checking for \$(VARIABLE).\n" if ($verbose); if ($whole =~ /\$\([\w\d]+\)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use \${VARIABLE}, instead of ". "\$(VARIABLE)."); } } # # whole file: PLIST_FILES and PLIST_DIRS # print "OK: checking PLIST_FILES and PLIST_DIRS.\n" if ($verbose); if ($whole =~ /\nPLIST_FILES.?=/ || $whole =~ /\nPLIST_DIRS.?=/) { if (-f 'pkg-plist') { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: You may remove pkg-plist ". "if you use PLIST_FILES and/or PLIST_DIRS."); } } # # whole file: USE_* used too late # pos($whole) = 0; if ($whole =~ /^\.include\s+$/gm) { print "OK: checking for USE_* used too late.\n" if ($verbose); my @use_early = qw( APACHE BZIP2 GNUSTEP IMAKE JAVA KDE(?:BASE|LIBS)_VER (?:LIB)?RUBY LINUX_PREFIX OPENSSL PHP PYTHON QT2? QT_VER X_PREFIX ZIP ); my @other_early = qw( EMACS_PORT_NAME OPTIONS ); my $earlypattern = join('|', 'USE_(?:'.join('|', @use_early).')', @other_early); while ($whole =~ /^($earlypattern)[+?:!]?=/gmo) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: $1 is set after ". "including bsd.port.pre.mk."); } } # # whole file: USE_* as a user-settable option # print "OK: checking for USE_* as a user-settable option.\n" if ($verbose); while ($whole =~ /\n\s*\.\s*(?:el)?if[^\n]*?\b(\w*USE_)(\w+)(?\![^\n]*\n#?\.error)/g) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: is $1$2 a user-settable option? ". "Consider using WITH_$2 instead.") if ($1.$2 ne 'USE_GCC'); } # # whole file: NO_CHECKSUM # # XXX Don't compress newlines since it messes up line number calculation. #$whole =~ s/\n#[^\n]*/\n/g; #$whole =~ s/\n\n+/\n/g; print "OK: checking NO_CHECKSUM.\n" if ($verbose); if ($whole =~ /\nNO_CHECKSUM/) { + $use_no_checksum = 1; my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: use of NO_CHECKSUM discouraged. ". "it is intended to be a user variable."); } # # whole file: USE_SIZE # print "OK: checking USE_SIZE.\n" if ($verbose); if ($whole =~ /\nUSE_SIZE/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use of USE_SIZE is no longer ". "required."); } # # whole file: MACHINE_ARCH # print "OK: checking MACHINE_ARCH.\n" if ($verbose); if ($whole =~ /\nMACHINE_ARCH/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: MACHINE_ARCH should never be ". "overridden."); } # # whole file: PKGNAME # print "OK: checking PKGNAME.\n" if ($verbose); if ($whole =~ /\nPKGNAME.?=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: PKGNAME is obsoleted by PORTNAME, ". "PORTVERSION, PKGNAMEPREFIX and PKGNAMESUFFIX."); } # # whole file: IS_INTERACTIVE/NOPORTDOCS # print "OK: checking IS_INTERACTIVE.\n" if ($verbose); if ($whole =~ /\nIS_INTERACTIVE/) { if ($whole !~ /defined\((BATCH|FOR_CDROM)\)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: use of IS_INTERACTIVE ". "discouraged. provide batch mode by using BATCH and/or ". "FOR_CDROM."); } } print "OK: checking for use of NOPORTDOCS.\n" if ($verbose); if ($whole =~ /NOPORTSDOC/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: NOPORTSDOC found. Do you ". "mean NOPORTDOCS?"); } if ($sharedocused && $whole !~ /defined\s*\(?NOPORTDOCS\)?/ && $whole !~ /def\s*\(?NOPORTDOCS\)?/ && $whole !~ m#(\$[\{\(]PREFIX[\}\)]|$localbase)/share/doc#) { &perror("WARN: $file: use \".if !defined(NOPORTDOCS)\" to wrap ". "installation of files into $localbase/share/doc."); } # # whole file: check for USE_GETTEXT # print "OK: checking for USE_GETTEXT without WITHOUT_NLS.\n" if ($verbose); if ($whole =~ /\nUSE_GETTEXT/ && $whole !~ /def(?:ined)?\s*\(?WITHOUT_NLS\)?/) { &perror("WARN: $file: Consider adding support for a WITHOUT_NLS ". "knob to conditionally disable gettext support."); } # + # whole file: check for deprecated commands + # + print "OK: checking for deprecated macros.\n" if $verbose; + %deprecated = ( + USE_LIBTOOL => 'USE_LIBTOOL_VER', + USE_AUTOCONF => 'USE_AUTOHEADER_VER', + USE_AUTOMAKE => 'USE_AUTOMAKE_VER', + WANT_LIBTOOL => 'WANT_LIBTOOL_VER', + WANT_AUTOCONF => 'WANT_AUTOCONF_VER', + WANT_AUTOMAKE => 'WANT_AUTOMAKE_VER', + USE_MESA => 'USE_GL', + ); + + for my $depmacro (keys %deprecated) { + if ($whole =~ /\n($depmacro)[+?:!]?=/) { + &perror("FATAL: $file: $depmacro is ". + "deprecated, use $deprecated{$1} instead"); + } + } + + # # whole file: direct use of command names # my %cmdnames = (); print "OK: checking direct use of command names.\n" if ($verbose); foreach my $i (qw( awk basename brandelf cat chmod chown cp cpio dialog dirname echo egrep expr false file find gmake grep gzcat ldconfig ln md5 mkdir mv objcopy paste patch pax perl printf rm rmdir ruby sed sh sort touch tr which xargs xmkmf )) { $cmdnames{$i} = "\$\{\U$i\E\}"; } $cmdnames{'env'} = '${SETENV}'; $cmdnames{'gunzip'} = '${GUNZIP_CMD}'; $cmdnames{'gzip'} = '${GZIP_CMD}'; $cmdnames{'install'} = '${INSTALL_foobaa}'; $cmdnames{'python'} = '${PYTHON_CMD}'; $cmdnames{'strip'} = '${STRIP_CMD}'; $cmdnames{'unzip'} = '${UNZIP_CMD}'; foreach my $i (qw(aclocal autoconf autoheader automake autoreconf autoupdate autoscan ifnames libtool libtoolize)) { $autocmdnames{$i} = "\$\{" . ( ( $i !~ /auto|aclocal|libtool/ ) ? "AUTO" : "" ) . "\U$i\E\}"; } # # ignore parameter string to echo command. # note that we leave the command as is, since we need to check the # use of echo itself. $j = $whole; $j =~ s/([ \t][\@\-]{0,2})(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+(?:"(?:\\'|\\"|[^"])*"|'(?:\\'|\\"|[^'])*')[ \t]*;?(\n?)/$1$2;$3/g; #" # ignore variables names in .for loops, but not what's at the end # of the for loop $j =~ s/(\.for +)([^ ]*)( .*)/$1$3/; foreach my $i (keys %cmdnames) { # XXX This is a hack. Really, we should break $j up into individual # lines, and go through each one. while ($j =~ /^(.*$i.*)$/gm) { my $curline = $1; my $lineno = &linenumber($`); if ($curline =~ /(^|\s+)[\@\-]{0,2}$i\b/ && $curline !~ /^[A-Z]+_TARGET[?+]?=[^\n]+$i/m && $curline !~ /^IGNORE(.)?=[^\n]+$i/m && $curline !~ /^BROKEN(.)?=[^\n]+$i/m && $curline !~ /^RESTRICTED(.)?=[^\n]+$i/m && $curline !~ /^NO_PACKAGE(.)?=[^\n]+$i/m && $curline !~ /^NO_CDROM(.)?=[^\n]+$i/m && $curline !~ /^MAINTAINER(.)?=[^\n]+$i/m && $curline !~ /^CATEGORIES(.)?=[^\n]+$i/m && $curline !~ /^#.+$/m && $curline !~ /^COMMENT(.)?=[^\n]+$i/m) { &perror("WARN: $file [$lineno]: possible direct use of ". "command \"$i\" found. use ". "$cmdnames{$i} instead."); } } } foreach my $i (keys %autocmdnames) { # XXX Same hack as above. while ($j =~ /^(.*($i\d*).*)$/gm) { my $lm = $1; my $sm = $2; my $lineno = &linenumber($`); if ($lm =~ /(^|\s+)[\@\-]{0,2}($i\d*)\b/ && $lm !~ /^[A-Z]+_TARGET[?+]?=[^\n]+($i\d*)/m && $lm !~ /^IGNORE(.)?=[^\n]+($i\d*)/m && $lm !~ /^BROKEN(.)?=[^\n]+($i\d*)/m && $lm !~ /^RESTRICTED(.)?=[^\n]+($i\d*)/m && $lm !~ /^NO_PACKAGE(.)?=[^\n]+($i\d*)/m && $lm !~ /^NO_CDROM(.)?=[^\n]+($i\d*)/m && $lm !~ /^MAINTAINER(.)?=[^\n]+($i\d*)/m && $lm !~ /^CATEGORIES(.)?=[^\n]+($i\d*)/m && $lm !~ /^#.+$/m && $lm !~ /^COMMENT(.)?=[^\n]+($i\d*)/m) { &perror("WARN: $file [$lineno]: possible direct use of ". "command \"$sm\" found. Use $autocmdnames{$i} ". "instead and set according USE_*_VER= flag"); } } } # # whole file: ldconfig must come with "true" command # if ($ldconfigwithtrue && $j =~ /(ldconfig|\$[{(]LDCONFIG[)}])/ && $j !~ /(\/usr\/bin\/true|\$[{(]TRUE[)}])/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: ldconfig must be used with ". "\"||\${TRUE}\"."); } # # whole file: ${GZIP_CMD} -9 (or any other number) # if ($j =~ /\${GZIP_CMD}\s+-(\w+(\s+-)?)*(\d)/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${GZIP_CMD} -$3\" ". "found. \${GZIP_CMD} includes \"-\${GZIP}\" which ". "sets the compression level."); } # # whole file: ${MKDIR} -p # if ($j =~ /\${MKDIR}\s+-p/) { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${MKDIR} -p\" ". "found. \${MKDIR} includes ". "\"-p\" by default."); } # # whole file: ${MACHINE_ARCH} # if ($j =~ /\${MACHINE_ARCH}\s*[!=]=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: MACHINE_ARCH should never be tested ". "directly; use ARCH instead."); } # # whole file: full path name # &abspathname($whole, $file); # # whole file: SITE_PERL # print "OK: checking SITE_PERL.\n" if ($verbose); if ($whole =~ /\nSITE_PERL[?:]?=/) { my $lineno = &linenumber($`); &perror("FATAL: $file [$lineno]: use of SITE_PERL discouraged. ". "it is set in bsd.port.mk."); } # # whole file: ${LOCALBASE}/lib/perl5/site_perl/${PERL_VER} # if ($j =~ m'\${(?:LOCALBASE|PREFIX)}/lib/perl5/site_perl/\${PERL_VER}') { my $lineno = &linenumber($`); &perror("WARN: $file [$lineno]: possible use of \"\${LOCALBASE}/lib/perl5/site_perl/\${PERL_VER}\" ". "found. use \"\${SITE_PERL}\" instead."); } # # whole file: USE_GNOME check # if ($whole =~ /^USE_GNOME[?:]?=\s*(.*)$/m) { if ($1 =~ /gnomehack/) { $use_gnome_hack = 1; } } # # whole file: check for NO_SIZE # if ($whole =~ /^NO_SIZE[?:]?=/m) { $use_no_size = 1; } # - # whole file: check for deprecated USE_MESA - # - if ($whole =~ /^USE_MESA[?:]?=/m) { - my $lineno = &linenumber($`); - &perror("WARN: $file [$lineno]: USE_MESA is deprecated". - ". use USE_GL instead."); - } - - # # slave port check # my $masterdir = $makevar{MASTERDIR}; if ($masterdir ne '' && $masterdir ne $makevar{'.CURDIR'}) { $slaveport = 1; print "OK: slave port detected, checking for inclusion of $masterdir/Makefile.\n" if ($verbose); if ($whole =~ /^\.\s*include\s*[<"]bsd\.port(?:\.post)?\.mk[">]/m) { &perror("FATAL: $file: supposedly non-slave port with". " .CURDIR != MASTERDIR"); } elsif ($whole =~ /^\.\s*include\s*[<"]bsd\.port\.pre\.mk[">]/m) { &perror("FATAL: $file: slave ports may not include". " bsd.port.pre.mk"); } if ($whole !~ /\n\.include\s+"\$\{MASTERDIR\}\/Makefile"\s*$/s) { &perror("FATAL: $file: the last line of a slave port's Makefile has to be". ' .include "${MASTERDIR}/Makefile"'); } print "OK: checking master port in $masterdir.\n" if ($verbose); if (! -e "$masterdir/Makefile") { &perror("WARN: unable to locate master port in $masterdir"); } if ($whole !~ /^MASTERDIR=\s*\$\{\.CURDIR\}(?:\/\.\.){1,2}(?:\/[\w\@.+-]+){1,2}\s*$/m) { &perror("WARN: $file: slave ports must define MASTERDIR=". '${.CURDIR}/..(/../)/'); } } else { #$slaveport = 0; print "OK: non-slave port detected, checking for anything after bsd.port(.post).mk.\n" if ($verbose); if ($whole !~ /\n\.include\s+\s*$/s) { &perror("FATAL: $file: the last line of Makefile has to be". ' .include '); } if ($whole =~ /^MASTERDIR\s*[+?:!]?\s*=/m) { &perror("WARN: $file: non-slave ports may not define MASTERDIR"); } } # # break the makefile into sections. # $tmp = $rawwhole; # keep comment, blank line, comment in the same section $tmp =~ s/(#.*\n)\n+(#.*)/$1$2/g; @sections = split(/\n\n+/, $tmp); for ($i = 0; $i <= $#sections; $i++) { if ($sections[$i] !~ /\n$/) { $sections[$i] .= "\n"; } } $idx = 0; # # section 1: comment lines. # print "OK: checking comment section of $file.\n" if ($verbose); my @linestocheck = split("\n", <=! ])/) { &perror("FATAL: $file: PORTNAME contains the illegal character \"$1\".". " You should modify \"$portname\"."); } elsif ($portname =~ /\$[\{\(].+[\}\)]/) { &perror("WARN: $file: using variable in PORTNAME.". " consider using PKGNAMEPREFIX and/or PKGNAMESUFFIX."); } elsif ($portname =~ /([^\w._@+-])/) { &perror("WARN: $file: using \"$1\" in PORTNAME.". " You should modify \"$portname\"."); } elsif ($portname =~ /-/ && $distname ne '') { &perror("WARN: $file: using hyphen in PORTNAME.". " consider using PKGNAMEPREFIX and/or PKGNAMESUFFIX."); } if ($portversion eq '') { &perror("FATAL: $file: PORTVERSION must be specified"); } if ($portversion =~ /^pl[0-9]*$/ || $portversion =~ /^[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9+]*)*$/) { print "OK: PORTVERSION \"$portversion\" looks fine.\n" if ($verbose); } elsif ($portversion =~ /^[^\-]*\$[{\(].+[\)}][^\-]*$/) { &perror("WARN: $file: using variable, \"$portversion\", as version ". "number"); } elsif ($portversion =~ /([-,_<>=! #*])/) { &perror("FATAL: $file: PORTVERSION must not contain \"$1\". ". "You should modify \"$portversion\"."); } else { &perror("FATAL: $file: PORTVERSION looks illegal. ". "You should modify \"$portversion\"."); } $pkg_version = -x '/usr/local/sbin/pkg_version' ? '/usr/local/sbin/pkg_version' : '/usr/sbin/pkg_version'; $versiondir = $ENV{VERSIONDIR} ? $ENV{VERSIONDIR} : '/var/db/chkversion'; $versionfile = "$versiondir/VERSIONS"; $useindex = !-r "$versionfile"; $versionfile = "$portsdir/$makevar{INDEXFILE}" if $useindex; if (-r "$versionfile") { print "OK: checking if PORTVERSION is going backwards.\n" if ($verbose); open VERSIONS, "<$versionfile"; while () { my($origin, $version) = ('', ''); chomp; next if /^(#|$)/; if ($useindex) { ($version, $origin) = split /\|/; $origin =~ s,^.*/([^/]+/[^/]+)/?$,$1,; } else { ($origin, $version) = split; } if ($origin eq $makevar{PKGORIGIN}) { my $newversion = $makevar{PKGNAME}; my $oldversion = $version; my $result = ''; $newversion =~ s/^.*-//; $oldversion =~ s/^.*-//; $result = `$pkg_version -t '$newversion' '$oldversion'`; chomp $result; if ($result eq '<') { &perror("FATAL: $file: $makevar{PKGNAME} < $version. ". "Choose another PORTVERSION or bump PORTEPOCH."); # $backwards{$origin} = "$pkgname{$origin} < $version"; } last; } } close VERSIONS; } # if DISTFILES have only single item, it is better to avoid DISTFILES # and to use combination of DISTNAME and EXTRACT_SUFX. # example: # DISTFILES=package-1.0.tgz # should be # DISTNAME= package-1.0 # EXTRACT_SUFX= .tgz if ($distfiles =~ /^\S+$/ && $distfiles !~ /:[^\/:]+$/) { $bogusdistfiles++; print "OK: seen DISTFILES with single item, checking value.\n" if ($verbose); &perror("WARN: $file: use of DISTFILES with single file ". "discouraged. distribution filename should be set by ". "DISTNAME and EXTRACT_SUFX."); if ($distfiles eq (($distname ne '') ? $distname : "$portname-$portversion") . $extractsufx) { &perror("WARN: $file: definition of DISTFILES not necessary. ". "DISTFILES is \${DISTNAME}/\${EXTRACT_SUFX} ". "by default."); } # display advice only in certain cases. #MICHAEL: will this work with multiple distfiles in this list? what about # doing the same sort of thing for DISTNAME, is it needed? if ($distfiles =~ /^\Q$i\E([\-.].+)$/) { &perror("WARN: $file: how about \"EXTRACT_SUFX=$1\"". ", instead of DISTFILES?"); } } # additional checks for committer. if ($committer && $has_lang_cat) { &perror("WARN: $file: be sure to include language code ". "\"$port_lang-\" ". "in the module alias name."); } if ($committer) { if (opendir(DIR, ".")) { my @tgz = grep(/\.tgz$/, readdir(DIR)); closedir(DIR); if (@tgz) { my $tgz = (2 <= @tgz) ? '{' . join(',', @tgz) . '}' : $tgz[0]; &perror("WARN: be sure to remove $portdir/$tgz ". "before committing the port."); } } } push(@varnames, qw( PORTNAME PORTVERSION PORTREVISION PORTEPOCH CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR PKGNAMEPREFIX PKGNAMESUFFIX DISTNAME EXTRACT_SUFX DISTFILES EXTRACT_ONLY )); # # section 3: PATCH_SITES/PATCHFILES(optional) # print "OK: checking second section of $file (PATCH*: optional).\n" if ($verbose); $tmp = $sections[$idx]; if ($tmp =~ /(PATCH_SITES|PATCH_SITE_SUBDIR|PATCHFILES|PATCH_DIST_STRIP)/) { &checkearlier($file, $tmp, @varnames); if ($tmp =~ /^PATCH_SITES=/) { print "OK: seen PATCH_SITES.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCH_SITE_SUBDIR=/) { print "OK: seen PATCH_SITE_SUBDIR.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCHFILES=/) { print "OK: seen PATCHFILES.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } if ($tmp =~ /^PATCH_DIST_STRIP=/) { print "OK: seen PATCH_DIST_STRIP.\n" if ($verbose); $tmp =~ s/^[^\n]+\n//; } &checkextra($tmp, 'PATCH_SITES', $file); $idx++; } push(@varnames, qw( PATCH_SITES PATCHFILES PATCH_DIST_STRIP )); # # section 4: MAINTAINER # print "OK: checking third section of $file (MAINTAINER).\n" if ($verbose); $tmp = $sections[$idx++]; &checkearlier($file, $tmp, @varnames); &checkorder('MAINTAINER', $tmp, $file, qw( MAINTAINER COMMENT )); $tmp = "\n" . $tmp; if ($tmp =~ /\nMAINTAINER\??=([^\n]+)/) { my $addr = $1; $addr =~ s/^\s*//; $addr =~ s/\s*$//; if ($addr =~ /[\s,<>()]/) { &perror("FATAL: $file: MAINTAINER should be a single address without comment."); } if ($addr !~ /^[^\@]+\@[\w\d\-\.]+$/) { &perror("FATAL: $file: MAINTAINER address, $addr, does not appear to be a valid email address."); } $tmp =~ s/\nMAINTAINER\??=[^\n]+//; } elsif ($whole !~ /\nMAINTAINER[?]?=/) { &perror("FATAL: $file: no MAINTAINER listed.") unless ($slaveport && $makevar{MAINTAINER} ne ''); } $tmp =~ s/\n\n+/\n/g; # check COMMENT if ($tmp !~ /\nCOMMENT(.)?=/) { &perror("FATAL: $file: COMMENT has to be there.") unless ($slaveport && $makevar{COMMENT} ne ''); } elsif ($1 ne '') { &perror("WARN: $file: unless this is a master port, COMMENT has to be set by \"=\", ". "not by \"$1=\".") unless ($masterport); } else { # check for correctness if (($makevar{COMMENT} !~ /^["0-9A-Z]/) || ($makevar{COMMENT} =~ m/\.$/)) { #" &perror("WARN: $file: COMMENT should begin with a capital, and end without a period"); } elsif (length($makevar{COMMENT}) > 70) { &perror("WARN: $file: COMMENT exceeds 70 characters limit."); } } push(@varnames, qw( MAINTAINER COMMENT )); # # section 5: *_DEPENDS (may not be there) # print "OK: checking fourth section of $file (*_DEPENDS).\n" if ($verbose); $tmp = $sections[$idx]; # NOTE: EXEC_DEPENDS is obsolete, so it should not be listed. @linestocheck = qw( EXTRACT_DEPENDS LIB_DEPENDS PATCH_DEPENDS BUILD_DEPENDS RUN_DEPENDS FETCH_DEPENDS DEPENDS DEPENDS_TARGET ); if ($tmp =~ /(LIB_|BUILD_|RUN_|FETCH_)?DEPENDS/) { &checkearlier($file, $tmp, @varnames); my %seen_depends; if (!defined $ENV{'PORTSDIR'}) { $ENV{'PORTSDIR'} = $portsdir; } foreach my $i (grep(/^[A-Z_]*DEPENDS[?+]?=/, split(/\n/, $tmp))) { $i =~ s/^([A-Z_]*DEPENDS)[?+]?=[ \t]*//; $j = $1; $seen_depends{$j}++; if ($j ne 'DEPENDS' && $i =~ /^\${([A-Z_]+DEPENDS)}\s*$/ && $seen_depends{$1} && $j ne $1) { print "OK: $j refers to $1, skipping checks.\n" if ($verbose); next; } print "OK: checking ports listed in $j.\n" if ($verbose); foreach my $k (split(/\s+/, $i)) { my @l = split(':', $k); print "OK: checking dependency value for $j.\n" if ($verbose); if (($j eq 'DEPENDS' && scalar(@l) != 1 && scalar(@l) != 2) || ($j ne 'DEPENDS' && scalar(@l) != 2 && scalar(@l) != 3)) { &perror("WARN: $file: wrong dependency value ". "for $j. $j requires ". ($j eq 'DEPENDS' ? "1 or 2 " : "2 or 3 "). "colon-separated tuples."); next; } my %m = (); if ($j eq 'DEPENDS') { $m{'dir'} = $l[0]; $m{'tgt'} = $l[1]; } else { $m{'dep'} = $l[0]; $m{'dir'} = $l[1]; $m{'tgt'} = $l[2]; } print "OK: dep=\"$m{'dep'}\", ". "dir=\"$m{'dir'}\", tgt=\"$m{'tgt'}\"\n" if ($verbose); # check USE_PERL5 if ($m{'dep'} =~ /^perl5(\.\d+)?$/) { &perror("WARN: $file: dependency to perl5 ". "listed in $j. consider using ". "USE_PERL5."); } # check USE_ICONV if ($m{'dep'} =~ /^(iconv\.\d+)$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_ICONV."); } # check USE_GETTEXT if ($m{'dep'} =~ /^(intl\.\d+)$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GETTEXT."); } # check USE_GMAKE if ($m{'dep'} =~ /^(gmake|\${GMAKE})$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GMAKE."); } # check USE_QT if ($m{'dep'} =~ /^(qt\d)+$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_QT."); } # check USE_GETOPT_LONG if ($m{'dep'} =~ /^(gnugetopt\.\d)+$/) { &perror("WARN: $file: dependency to $1 ". "listed in $j. consider using ". "USE_GETOPT_LONG."); + } + + # check LIBLTDL + if ($m{'dep'} =~ /^(ltdl\.\d)+$/) { + &perror("WARN: $file: dependency to $1 ". + "listed in $j. consider using ". + "USE_LIBLTDL."); } # check backslash in LIB_DEPENDS if ($osname eq 'NetBSD' && $j eq 'LIB_DEPENDS' && $m{'dep'} =~ /\\\\./) { &perror("WARN: $file: use of backslashes in ". "$j is deprecated."); } # check for PREFIX if ($m{'dep'} =~ /\${PREFIX}/) { &perror("FATAL: $file: \${PREFIX} must not be ". "contained in *_DEPENDS. ". "use \${LOCALBASE} or ". "\${X11BASE} instead."); } # check port dir existence $k = $m{'dir'}; $k =~ s/\${PORTSDIR}/$ENV{'PORTSDIR'}/; $k =~ s/\$[\({]PORTSDIR[\)}]/$ENV{'PORTSDIR'}/; if (! -d $k) { &perror("WARN: $file: no port directory $k ". "found, even though it is ". "listed in $j."); } else { print "OK: port directory $k found.\n" if ($verbose); } } } foreach my $i (@linestocheck) { $tmp =~ s/$i[?+]?=[^\n]+\n//g; } &checkextra($tmp, '*_DEPENDS', $file); $idx++; } push(@varnames, @linestocheck); &checkearlier($file, $tmp, @varnames); # # Makefile 6: check the rest of file # print "OK: checking the rest of the $file.\n" if ($verbose); $tmp = join("\n\n", @sections[$idx .. scalar(@sections)-1]); $tmp = "\n" . $tmp; # to make the begin-of-line check easier &checkearlier($file, $tmp, @varnames); # check WRKSRC/NO_WRKSUBDIR # # do not use DISTFILES/DISTNAME to control over WRKSRC. # DISTNAME is for controlling distribution filename. # example: # DISTNAME= package # DISTFILES=package-1.0.tgz # should be # DISTNAME= package-1.0 # EXTRACT_SUFX=.tgz # WRKSRC= ${WRKDIR}/package # print "OK: checking WRKSRC.\n" if ($verbose); $wrksrc = $nowrksubdir = ''; $wrksrc = $1 if ($tmp =~ /\nWRKSRC[+?]?=[ \t]*([^\n]*)\n/); $nowrksubdir = $1 if ($tmp =~ /\nNO_WRKSUBDIR[+?]?=[ \t]*([^\n]*)\n/); if ($nowrksubdir eq '') { $realwrksrc = $wrksrc ? "$wrksrc/$distname" : "\${WRKDIR}/$distname"; } else { $realwrksrc = $wrksrc ? $wrksrc : '${WRKDIR}'; } print "OK: WRKSRC seems to be $realwrksrc.\n" if ($verbose); if ($nowrksubdir eq '') { print "OK: no NO_WRKSUBDIR, checking value of WRKSRC.\n" if ($verbose); if ($wrksrc eq 'work' || $wrksrc =~ /^$[\{\(]WRKDIR[\}\)]/) { &perror("WARN: $file: WRKSRC is set to meaningless value ". "\"$1\".". ($nowrksubdir eq '' ? " use \"NO_WRKSUBDIR=yes\" instead." : "")); } if ($bogusdistfiles) { if ($distname ne '' && $wrksrc eq '') { &perror("WARN: $file: do not use DISTFILES and DISTNAME ". "to control WRKSRC. how about ". "\"WRKSRC=\${WRKDIR}/$distname\"?"); } else { &perror("WARN: $file: DISTFILES/DISTNAME affects WRKSRC. ". "take caution when changing them."); } } } else { print "OK: seen NO_WRKSUBDIR, checking value of WRKSRC.\n" if ($verbose); if ($wrksrc eq 'work' || $wrksrc =~ /^$[\{\(]WRKDIR[\}\)]/) { &perror("WARN: $file: definition of WRKSRC not necessary. ". "WRKSRC is \${WRKDIR} by default."); } } # check RESTRICTED/NO_CDROM/NO_PACKAGE print "OK: checking RESTRICTED/NO_CDROM/NO_PACKAGE.\n" if ($verbose); if ($committer && $tmp =~ /\n(RESTRICTED|NO_CDROM|NO_PACKAGE)[+?]?=/) { &perror("WARN: $file: \"$1\" found. do not forget to update ". "ports/LEGAL."); } # check NO_CONFIGURE/NO_PATCH print "OK: checking NO_CONFIGURE/NO_PATCH.\n" if ($verbose); if ($tmp =~ /\n(NO_CONFIGURE|NO_PATCH)[+?]?=/) { &perror("FATAL: $file: \"$1\" was obsoleted. remove this."); } # check MAN[1-9LN] print "OK: checking MAN[0-9LN].\n" if ($verbose); foreach my $i (keys %plistmanall) { print "OK: pkg-plist MAN$i=$plistmanall{$i}\n" if ($verbose); } foreach my $i (split(//, $manchapters)) { if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { print "OK: Makefile MAN\U$i\E=$1\n" if ($verbose); } } foreach my $i (split(//, $manchapters)) { next if ($i eq ''); if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { @mman = grep($_ !~ /^\s*$/, split(/\s+/, $1)); @pman = grep($_ !~ /^\s*$/, split(/\s+/, $plistmanall{$i})); foreach my $j (@mman) { print "OK: checking $j (Makefile)\n" if ($verbose); if ($automan && grep($_ eq $j, @pman)) { &perror("FATAL: $file: duplicated manpage ". "entry $j: content of ". "MAN\U$i\E will be automatically ". "added to pkg-plist."); } elsif (!$automan && !grep($_ eq $j, @pman)) { &perror("WARN: $file: manpage $j ". "MAN\U$i\E but not in pkg-plist."); } } foreach my $j (@pman) { print "OK: checking $j (pkg-plist)\n" if ($verbose); if (!grep($_ eq $j, @mman)) { &perror("WARN: $file: manpage $j in pkg-plist ". "but not in MAN\U$i\E."); } } } else { if ($plistmanall{$i}) { if ($manstrict) { &perror("FATAL: $file: manpage for chapter ". "$i must be listed in ". "MAN\U$i\E. "); } else { &perror("WARN: $file: manpage for chapter ". "$i should be listed in ". "MAN\U$i\E, ". "even if compression is ". "not necessary."); } } if ($mancompress && $plistman{$i}) { &perror("WARN: $file: MAN\U$i\E will help you ". "compressing manual page in chapter ". "\"$i\"."); } elsif (!$mancompress && $plistmangz{$i}) { &perror("WARN: $file: MAN\U$i\E will help you ". "uncompressing manual page in chapter ". "\"$i\"."); } } } if ($tmp !~ /MANLANG/ && scalar(keys %manlangs)) { $i = (keys %manlangs)[0]; &perror("WARN: $file: how about using MANLANG for ". "designating manual language, such as \"$i\"?"); } # check INFO print "OK: checking INFO.\n" if ($verbose); if ($autoinfo && $tmp =~ /\nINFO=\s*([^\n]*)\n/) { my @minfo = grep($_ !~ /^\s*$/, split(/\s+/, $1)); foreach $i (@minfo) { if ($i =~ /\.info(-\d+)?$/) { &perror("FATAL: $file: do not include the .info extension ". "on files listed in the INFO macro."); } } } # check USE_X11 and USE_IMAKE if ($tmp =~ /\nUSE_IMAKE[?+]?=/ && $tmp =~ /\n(USE_X11)[?+]?=/) { &perror("WARN: $file: since you already have USE_IMAKE, ". "you don't need $1."); } # check USE_X11 and USE_IMAKE if ($newxdef && $tmp =~ /\nUSE_IMAKE[?+]?=/ && $tmp =~ /\n(USE_X_PREFIX)[?+]?=/) { &perror("WARN: $file: since you already have USE_IMAKE, ". "you don't need $1."); } # check USE_X11 and USE_X_PREFIX if ($newxdef && $tmp =~ /\nUSE_X11[?+]?=/ && $tmp !~ /\nUSE_X_PREFIX[?+]?=/) { &perror("FATAL: $file: meaning of USE_X11 was changed in Aug 1998. ". "use USE_X_PREFIX instead."); } # check direct use of important make targets. if ($tmp =~ /\n(fetch|extract|patch|configure|build|install):/) { &perror("FATAL: $file: direct redefinition of make target \"$1\" ". "discouraged. redefine \"do-$1\" instead."); } # check for incorrect use of the pre-everything target. if ($tmp =~ /\npre-everything:[^:]/) { &perror("FATAL: $file: use pre-everything:: instead of pre-everything:"); } if ($tmp =~ /^pre-patch:/m && $use_gnome_hack) { &perror("FATAL: $file: pre-patch target overwrites gnomehack component. ". "use post-patch instead."); } 1; } sub perror { my(@msg) = @_; if ($msg[0] =~ /^FATAL/) { $err++; } else { $warn++; } print join("\n", @msg) . "\n"; } sub checkextra { my($str, $section, $file) = @_; $str = "\n" . $str if ($str !~ /^\n/); $str =~ s/\n#[^\n]*/\n/g; $str =~ s/\n\.[^\n]+/\n/g; $str =~ s/\n\n+/\n/g; $str =~ s/^\s+//; $str =~ s/\s+$//; return if ($str eq ''); if ($str =~ /^([\w\d]+)/) { &perror("WARN: $file: extra item placed in the ". "$section section, ". "for example, \"$1\"."); } else { &perror("WARN: $file: extra item placed in the ". "$section section."); } } sub checkorder { my($section, $str, $file, @order) = @_; my(@items, $i, $j, $k, $invalidorder); print "OK: checking the order of $section section.\n" if ($verbose); @items = (); foreach my $i (split("\n", $str)) { $i =~ s/[+?]?=.*$//; push(@items, $i); } @items = reverse(@items); $j = -1; $invalidorder = 0; while (scalar(@items)) { $i = pop(@items); $k = 0; while ($k < scalar(@order) && $order[$k] ne $i) { $k++; } if ($order[$k] eq $i) { if ($k < $j) { &perror("FATAL: $file: $i appears out-of-order."); $invalidorder++; } else { print "OK: seen $i, in order.\n" if ($verbose); } $j = $k; # This if condition tests for .if, .else (in all forms), # .for and .endfor and .include } elsif ($i !~ m/^\.(if|el|endif|for|endfor|include)/) { &perror("FATAL: $file: extra item \"$i\" placed in the ". "$section section."); } } if ($invalidorder) { &perror("FATAL: $file: order must be " . join('/', @order) . '.'); } else { print "OK: $section section is ordered properly.\n" if ($verbose); } } sub checkearlier { my($file, $str, @varnames) = @_; my($i); print "OK: checking items that has to appear earlier.\n" if ($verbose); foreach my $i (@varnames) { if ($str =~ /\n$i\??=/) { &perror("WARN: $file: \"$i\" has to appear earlier."); } } } sub linenumber { my $text = shift; my @lines; @lines = split /\n/, $text; return scalar(@lines); } sub abspathname { my($str, $file) = @_; my($s, $i, %cmdnames); my($pre); # trim all trailing backslash and newline $str =~ s/\\\n\s*/ /g; # ignore parameter string to reinplace command $str =~ s/([ \t][\@-]?(?:sed|\$[\{\(]SED[\}\)]|\$[\{\(]REINPLACE_CMD[\}\)]))((?:\s+\-\w+)*\s+(?:"(?:\\"|[^"\n])*"|'(?:\\'|[^'\n])*'))+(.*)/$1$3/g; #' # ignore parameter string to echo command $str =~ s/[ \t][\@-]?(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+("(\\'|\\"|[^"])*"|'(\\'|\\"|[^"])*')[ \t]*[;\n]//; #' print "OK: checking direct use of full pathnames in $file.\n" if ($verbose); foreach my $s (split(/\n+/, $str)) { $i = ''; if ($s =~ /(^|[ \t\@'"-])(\/[\w\d])/) { #' # suspected pathnames are recorded. $i = $2 . $'; $pre = $` . $1; if ($pre =~ /MASTER_SITE_SUBDIR/) { # MASTER_SITE_SUBDIR lines are ok. $i = ''; } } if ($i ne '') { $i =~ s/\s.*$//; $i =~ s/['"].*$//; #' $i = substr($i, 0, 20) . '...' if (20 < length($i)); &perror("WARN: $file: possible use of absolute pathname ". "\"$i\".") unless ($i =~ m,^/compat/,); } } print "OK: checking direct use of pathnames, phase 1.\n" if ($verbose); %cmdnames = split(/\n|\t+/, <