#!@PREFIX@/bin/perl # # portlint - lint for package directory # # implemented by: # Jun-ichiro itojun Itoh # Yoshishige Arai # visit ftp://ftp.foretune.co.jp/pub/tools/portlint/ for latest version. # # Copyright(c) 1997 by Jun-ichiro Itoh . # All rights reserved. # Freely redistributable. Absolutely no warranty. # # From Id: portlint.pl,v 1.64 1998/02/28 02:34:05 itojun Exp # $NetBSD: pkglint.pl,v 1.33 2000/07/20 13:53:41 rh Exp $ # # This version contains some changes necessary for NetBSD packages # done by Hubert Feyrer and # Thorsten Frueauf # use Getopt::Std; $err = $warn = 0; $extrafile = $parenwarn = $committer = $verbose = $newport = 0; $contblank = 1; $portdir = '.'; # default setting - for FreeBSD $portsdir = '/usr/ports'; $rcsidstr = 'Id'; $multiplist = 0; $ldconfigwithtrue = 0; $rcsidinplist = 0; $mancompress = 1; $manstrict = 0; $manchapters = '123456789ln'; $localbase = "/usr/local"; getopts('habcNB:v'); if ($opt_h) { ($prog) = ($0 =~ /([^\/]+)$/); print STDERR <, <$portdir/pkg/*>)) { next if (! -T $i); $i =~ s/^\Q$portdir\E\///; next if (defined $checker{$i}); if ($i =~ /pkg\/PLIST$/ || ($multiplist && $i =~ /pkg\/PLIST/)) { unshift(@checker, $i); $checker{$i} = 'checkplist'; } else { push(@checker, $i); $checker{$i} = 'checkpathname'; } } } foreach $i (<$portdir/patches/patch-*>) { next if (! -T $i); $i =~ s/^\Q$portdir\E\///; next if (defined $checker{$i}); push(@checker, $i); $checker{$i} = 'checkpatch'; } if (-f "$portdir/files/patch-sum") { $i="files/patch-sum"; next if (defined $checker{$i}); push(@checker, $i); $checker{$i} = 'checkpatchsum'; } { # Make sure there's a files/patch-sum if there are patches $patches=0; patch: foreach $i (<$portdir/patches/patch-*>) { if ( -T "$i" ) { $patches=1; last patch; } } if ($patches && ! -f "$portdir/files/patch-sum" ) { &perror("WARN: no $portdir/files/patch-sum file. Please run 'make makepatchsum'."); } } if (-e <$portdir/files/md5>) { $i = ; next if (defined $checker{$i}); push(@checker, $i); $checker{$i} = 'checkmd5'; } foreach $i (@checker) { print "OK: checking $i.\n"; if (! -f "$portdir/$i") { &perror("FATAL: no $i in \"$portdir\"."); } else { $proc = $checker{$i}; &$proc($i) || &perror("Cannot open the file $i\n"); if ($i !~ /^patches\//) { &checklastline($i) || &perror("Cannot open the file $i\n"); } } } if (-e <$portdir/files/md5> ) { if ( $seen_NO_CHECKSUM ) { &perror("WARN: NO_CHECKSUM set, but files/md5 exists. Please remove it."); } } else { if ( ! $seen_NO_CHECKSUM ) { &perror("WARN: no $portdir/files/md5 file. Please run 'make makesum'."); } } if (! -f "$portdir/pkg/PLIST" and ! -f "$portdir/pkg/PLIST-mi" and ! $seen_PLIST_SRC and ! $seen_NO_PKG_REGISTER ) { &perror("WARN: no PLIST or PLIST-mi, and PLIST_SRC and NO_PKG_REGISTER unset.\n Are you sure PLIST handling is ok?"); } if ($committer) { if (scalar(@_ = <$portdir/work*/*>) || -d "$portdir/work*") { &perror("WARN: be sure to cleanup $portdir/work* ". "before committing the package."); } if (scalar(@_ = <$portdir/*/*~>) || scalar(@_ = <$portdir/*~>)) { &perror("WARN: for safety, be sure to cleanup ". "emacs backup files before committing the package."); } if (scalar(@_ = <$portdir/*/*.orig>) || scalar(@_ = <$portdir/*.orig>) || scalar(@_ = <$portdir/*/*.rej>) || scalar(@_ = <$portdir/*.rej>)) { &perror("WARN: for safety, be sure to cleanup ". "patch backup files before committing the package."); } } if ($err || $warn) { print "$err fatal errors and $warn warnings found.\n" } else { print "looks fine.\n"; } exit $err; # # pkg/COMMENT, pkg/DESCR # sub checkdescr { local($file) = @_; local(%maxchars) = ('pkg/COMMENT', 70, 'pkg/DESCR', 80); local(%maxlines) = ('pkg/COMMENT', 1, 'pkg/DESCR', 24); local(%errmsg) = ('pkg/COMMENT', "must be one-liner.", 'pkg/DESCR', "exceeds $maxlines{'pkg/DESCR'} ". "lines, make it shorter if possible."); local($longlines, $linecnt, $tmp) = (0, 0, ""); open(IN, "< $portdir/$file") || return 0; while () { $linecnt++; $longlines++ if ($maxchars{$file} < length($_)); $tmp .= $_; } 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. $file should be ". "plain ascii file."); } if ($file eq 'pkg/COMMENT' && $tmp =~ /\.$/i) { &perror("WARN: $file should not end with a '.' (period)."); } if ($file eq 'pkg/COMMENT' && $tmp =~ /^(a|an) /i) { &perror("WARN: $file should not begin with '$1 '."); } if ($file eq 'pkg/COMMENT' && ($tmp =~ /^\s/ || $tmp =~ /\s\n$/)) { &perror("WARN: $file should not not have any leading or ". "trailing whitespace."); } close(IN); } # # files/patch-sum # sub checkpatchsum { local($file) = @_; # files/patch-sum local(%inpatchsumfile); undef(%seen); open(SUM,"<$portdir/$file") || return 0; while() { next if !/^MD5 \(([^)]+)\) = (.*)$/; $patch=$1; $sum=$2; # bitch about *~ if ($patch =~ /~$/) { &perror("WARN: possible backup file '$patch' in $portdir/$file?"); } if (-T "$portdir/patches/$patch") { $calcsum=`sed -e '/\$NetBSD.*/d' $portdir/patches/$patch | md5`; chomp($calcsum); if ( "$sum" ne "$calcsum" ) { &perror("FATAL: checksum of $patch differs between $portdir/$file and\n" ." $portdir/patches/$patch. Rerun 'make makepatchsum'."); } } else { &perror("FATAL: patchfile '$patch' is in $file\n" ." but not in $portdir/patches/$patch. Rerun 'make makepatchsum'."); } $inpatchsumfile{$patch} = 1; } close(SUM); foreach $patch ( <$portdir/patches/patch-*> ) { $patch =~ /\/([^\/]+)$/; if (! $inpatchsumfile{$1}) { &perror("FATAL: patchsum of '$1' is in $portdir/patches/$1 but not in\n" ." $file. Rerun 'make makepatchsum'."); } } return 1; } # # pkg/PLIST # sub checkplist { local($file) = @_; local($curdir) = ($localbase); local($inforemoveseen, $infoinstallseen, $infoseen) = (0, 0, 0); local($infobeforeremove, $infoafterinstall) = (0, 0); local($infooverwrite) = (0); local($rcsidseen) = 0; open(IN, "< $portdir/$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 ($_ =~ /^\@/) { if ($_ =~ /^\@(cwd|cd)[ \t]+(\S+)/) { $curdir = $2; } elsif ($_ =~ /^\@unexec[ \t]+rmdir/) { &perror("WARN: use \"\@dirrm\" ". "instead of \"\@unexec rmdir\"."); } elsif ($_ =~ /^\@exec[ \t]+(.*\/)?install-info/) { $infoinstallseen = $. } elsif ($_ =~ /^\@unexec[ \t]+(.*\/)?install-info[ \t]+--delete/) { $inforemoveseen = $. } elsif ($_ =~ /^\@(exec|unexec)/) { if ($ldconfigwithtrue && /ldconfig/ && !/\/usr\/bin\/true/) { &perror("FATAL: $file $.: ldconfig ". "must be used with ". "\"||/usr/bin/true\"."); } } elsif ($_ =~ /^\@(comment)/) { $rcsidseen++ if (/\$$rcsidstr[:\$]/); } elsif ($_ =~ /^\@(dirrm|option)/) { ; # no check made } elsif ($_ =~ /^\@(mode|owner|group)/) { &perror("WARN: \"\@mode/owner/group\" are ". "deprecated, please use chmod/". "chown/chgrp in the pkg Makefile ". "and let tar do the rest."); } else { &perror("WARN: $file $.: ". "unknown PLIST directive \"$_\""); } next; } if ($_ =~ /^\//) { &perror("FATAL: $file $.: use of full pathname ". "disallowed."); } if ($_ =~ /^info\/.*info(-[0-9]+)?$/) { $infoseen = $.; $infoafterinstall++ if ($infoinstallseen); $infobeforeremove++ if (!$inforemoveseen); } if ($_ =~ /^info\/dir$/) { &perror("FATAL: \"info/dir\" should not be listed in ". "$file. use install-info to add/remove ". "an entry."); $infooverwrite++; } if ($_ =~ m#man/([^/]+/)?man([$manchapters])/(.+\.[$manchapters])(\.gz)?#) { # was bugg for manpages w/ . in name - HF if ($osname eq "FreeBSD") { 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: seen installation to share/doc in $file. ". "($curdir/$_)\n" if ($verbose); $sharedocused++; } } if ($rcsidinplist && !$rcsidseen) { &perror("FATAL: RCS tag \"\$$rcsidstr\$\" must be present ". "in $file as \@comment.") } if (!$infoseen) { close(IN); return 1; } if (!$infoinstallseen) { if ($infooverwrite) { &perror("FATAL: \"\@exec install-info\" must be used ". "to add/delete entries into \"info/dir\"."); } &perror("FATAL: \"\@exec install-info\" must be placed ". "after all the info files."); } elsif ($infoafterinstall) { &perror("FATAL: move \"\@exec install-info\" line to make ". "sure that it is placed after all the info files. ". "(currently on line $infoinstallseen in $file)"); } if (!$inforemoveseen) { &perror("FATAL: \"\@unexec install-info --delete\" must ". "be placed before any of the info files listed."); } elsif ($infobeforeremove) { &perror("FATAL: move \"\@exec install-info --delete\" ". "line to make sure ". "that it is placed before any of the info files. ". "(currently on line $inforemoveseen in $file)"); } close(IN); } # # misc files # sub checkpathname { local($file) = @_; local($whole); open(IN, "< $portdir/$file") || return 0; $whole = ''; while () { $whole .= $_; } &abspathname($whole, $file); close(IN); } sub checklastline { local($file) = @_; local($whole); open(IN, "< $portdir/$file") || return 0; $whole = ''; while () { $whole .= $_; } if ($whole eq "") { &perror("FATAL: $file is empty."); } else { if ($whole !~ /\n$/) { &perror("FATAL: the last line of $file 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 { local($file) = @_; local($rcsidseen) = 0; local($whole); if ($file =~ /.*~$/) { &perror("WARN: is $file a backup file? If so, please remove it \n" ." and rerun 'make makepatchsum'"); } open(IN, "< $portdir/$file") || return 0; $whole = ''; while () { $rcsidseen++ if /\$$rcsidstr[:\$]/; $whole .= $_; } if ($committer && $whole =~ /.\$(Author|Date|Header|Id|Locker|Log|Name|RCSfile|Revision|Source|State|NetBSD)[:\$]/) { # XXX # RCS ID in very first line is ok, to identify version # of patch (-> only warn if there's something before the # actual $RCS_ID$, not on BOF - '.' won't match there) &perror("WARN: $file includes possible RCS tag \"\$$1\$\". ". "use binary mode (-ko) on commit/import."); } if (!$rcsidseen) { &perror("FATAL: RCS tag \"\$$rcsidstr\$\" must be present ". "in patch $file.") } close(IN); } sub checkmd5 { local($file) = @_; local($rcsidseen) = 0; open(IN, "< $portdir/$file") || return 0; while () { $rcsidseen++ if /\$$rcsidstr[:\$]/; } if (!$rcsidseen) { &perror("FATAL: RCS tag \"\$$rcsidstr\$\" must be present ". "in md5 $file.") } close(IN); } # # Makefile # sub checkmakefile { local($file) = @_; local($rawwhole, $whole, $idx, @sections); local($tmp); local($i, $j, $k, $l); local(@varnames) = (); local($distfiles, $pkgname, $distname, $extractsufx) = ('', '', '', ''); local($bogusdistfiles) = (0); local($realwrksrc, $wrksrc, $nowrksubdir) = ('', '', ''); local(@mman, @pman); open(IN, "< $portdir/$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"); } # # 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; print "OK: checking contiguous blank lines in $file.\n" if ($verbose); $i = "\n" x ($contblank + 2); if ($whole =~ /$i/) { &perror("FATAL: contiguous blank lines (> $contblank lines) found ". "in $file at line " . int(split(/\n/, $`)) . "."); } # # whole file: $(VARIABLE) # if ($parenwarn) { print "OK: checking for \$(VARIABLE).\n" if ($verbose); if ($whole =~ /\$\([\w\d]+\)/) { &perror("WARN: use \${VARIABLE}, instead of ". "\$(VARIABLE)."); } } # # whole file: IS_INTERACTIVE/NOPORTDOCS # $whole =~ s/\n#[^\n]*/\n/g; $whole =~ s/\n\n+/\n/g; print "OK: checking IS_INTERACTIVE.\n" if ($verbose); if ($whole =~ /\nIS_INTERACTIVE/) { if ($whole !~ /defined\((BATCH|FOR_CDROM)\)/) { &perror("WARN: 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 ($sharedocused && $whole !~ /defined\(NOPORTDOCS\)/ && $whole !~ m#(\$[\{\(]PREFIX[\}\)]|$localbase)/share/doc#) { &perror("WARN: use \".if !defined(NOPORTDOCS)\" to wrap ". "installation of files into $localbase/share/doc.") if $osname ne "NetBSD"; # how do you get this out of PLIST? } print "OK: checking for PLIST_SRC.\n" if ($verbose); if ($whole =~ /\nPLIST_SRC/) { $seen_PLIST_SRC=1; } print "OK: checking for NO_PKG_REGISTER.\n" if ($verbose); if ($whole =~ /\nNO_PKG_REGISTER/) { $seen_NO_PKG_REGISTER=1; } print "OK: checking for NO_CHECKSUM.\n" if ($verbose); if ($whole =~ /\nNO_CHECKSUM/) { $seen_NO_CHECKSUM=1; } print "OK: checking USE_PKGLIBTOOL.\n" if ($verbose); if ($whole =~ /\nUSE_PKGLIBTOOL/) { &perror("WARN: use of USE_PKGLIBTOOL discouraged, ". "use USE_LIBTOOL instead."); } print "OK: checking NO_CDROM.\n" if ($verbose); if ($whole =~ /\nNO_CDROM/) { &perror("WARN: use of NO_CDROM discouraged, ". "use NO_BIN_ON_CDROM and/or NO_SRC_ON_CDROM instead."); } print "OK: checking NO_PACKAGE.\n" if ($verbose); if ($whole =~ /\nNO_PACKAGE/) { &perror("WARN: use of NO_PACKAGE to enforce license restrictions ". "is deprecated."); } # # whole file: direct use of command names # print "OK: checking direct use of command names.\n" if ($verbose); foreach $i (split(/\s+/, <