Index: head/devel/portlint/Makefile =================================================================== --- head/devel/portlint/Makefile (revision 40121) +++ head/devel/portlint/Makefile (revision 40122) @@ -1,33 +1,33 @@ # 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.3.1 +PORTVERSION= 2.3.2 CATEGORIES= devel MASTER_SITES= # none DISTFILES= # none MAINTAINER= mharo@FreeBSD.org NO_BUILD= yes NO_WRKSUBDIR= yes USE_PERL5= yes SRC= ${.CURDIR}/src MAN1= portlint.1 do-fetch: @${DO_NADA} do-install: ${INSTALL_SCRIPT} ${SRC}/portlint.pl ${PREFIX}/bin/portlint ${INSTALL_MAN} ${SRC}/portlint.1 ${MAN1PREFIX}/man/man1 .include Property changes on: head/devel/portlint/Makefile ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.43 \ No newline at end of property +1.44 \ No newline at end of property Index: head/devel/portlint/src/portlint.pl =================================================================== --- head/devel/portlint/src/portlint.pl (revision 40121) +++ head/devel/portlint/src/portlint.pl (revision 40122) @@ -1,1655 +1,1686 @@ #! /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.28.2.1 2000/04/24 02:12:36 mharo Exp $ # use vars qw/ $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 = 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 $manchapters = '123456789ln'; my $localbase = '/usr/local'; my %lang_pref = qw( chinese zh french fr german de hebrew iw japanese ja korean ko russian ru vietnamese vi ); my @lang_cat = keys %lang_pref; my @lang_pref = values %lang_pref; my $re_lang_pref = '(' . join('|', @lang_pref) . ')-'; my ($prog) = ($0 =~ /([^\/]+)$/); sub usage { print STDERR <); close(MK); my $cmd = join(' -V MASTER_SITE_', "make $makeenv -f - all", @site_groups); my $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{COMMENT}, $makevar{DESCR}, 'Makefile', $makevar{MD5_FILE}); my %checker = ( $makevar{COMMENT} => 'checkdescr', $makevar{DESCR} => 'checkdescr', 'Makefile' => 'checkmakefile', $makevar{MD5_FILE} => 'TRUE' ); if ($extrafile) { my @files = ( <$makevar{SCRIPTDIR}/*>, @makevar{COMMENT,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 (! -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 ($i !~ m@/files/patch-@) { + if ($proc ne 'checkpatch') { &checklastline($i) || &perror("Cannot open the file $i\n"); } } } if ($committer) { - if (scalar(@_ = ) || -d "work") { - &perror("FATAL: be sure to cleanup $portdir/work ". - "before committing the port."); + 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."); + $problem = 1; + } elsif (-d && scalar(@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; + } } - if (scalar(@_ = <*/*~>) || scalar(@_ = <*~>)) { - &perror("FATAL: for safety, be sure to cleanup ". - "editor backup files before committing the port."); - } - if (scalar(@_ = <*/*.orig>) || scalar(@_ = ) - || scalar(@_ = <*/*.rej>) || scalar(@_ = <*.rej>)) { - &perror("FATAL: for safety, be sure to cleanup ". - "patch backup files before committing the port."); - } + + find(\&find_proc, '.'); } 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 { my($file) = @_; my(%maxchars) = ($makevar{COMMENT}, 70, $makevar{DESCR}, 80); my(%maxlines) = ($makevar{COMMENT}, 1, $makevar{DESCR}, 24); my(%errmsg) = ($makevar{COMMENT}, "must be one-liner.", $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. $file should be ". "plain ascii file."); } 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:"); } } if ($file =~ /\bpkg-comment/) { if (($tmp !~ /^["0-9A-Z]/) || ($tmp =~ m/\.$/)) { #" &perror("WARN: pkg-comment should begin with a capital, and end without a period"); } } close(IN); } # # pkg-plist # sub checkplist { my($file) = @_; my($curdir) = ($localbase); my($inforemoveseen, $infoinstallseen, $infoseen) = (0, 0, 0); my($infobeforeremove, $infoafterinstall) = (0, 0); my($infooverwrite) = (0); my($rcsidseen) = (0); my(@exec_info) = (); my(@unexec_info) = (); my(@infofile) = (); 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 ($_ =~ /^\@/) { if ($_ =~ /^\@(cwd|cd)[ \t]+(\S+)/) { $curdir = $2; } elsif ($_ =~ /^\@unexec[ \t]+rmdir/) { if ($_ !~ /true$/) { &perror("WARN: use \"\@dirrm\" ". "instead of \"\@unexec rmdir\"."); } } elsif ($_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { $infoinstallseen = $.; push(@exec_info, $1); } elsif ($_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { $inforemoveseen = $.; push(@unexec_info, $1); } 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: \@$1 should not be needed in pkg-plist"); } 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$/) { &perror("WARN: $file $.: installing libtool archives, ". "please use USE_LIBTOOL in Makefile if possible"); } if ($_ =~ /\.so(\.\d+)?$/ && $makevar{INSTALLS_SHLIB} eq '') { &perror("WARN: $file $.: installing shared libraries, ". "please define INSTALLS_SHLIB as appropriate"); } if ($_ =~ /^info\/.*info(-[0-9]+)?$/) { $infoseen = $.; $infoafterinstall++ if ($infoinstallseen); $infobeforeremove++ if (!$inforemoveseen); push(@infofile, $_); } 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)?$#) { 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++; } } # 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: you need an '\@exec install-info \%D/$if \%D/info/dir' line in your pkg-plist"); } if ($unexec_install !~ m/\%D\/$if/) { &perror("FATAL: you need an '\@unexec install-info --delete \%D/$if \%D/info/dir' line in your pkg-plist"); } } 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: install-info must be used to ". "add/delete entries into \"info/dir\"."); } &perror("FATAL: \"\@exec install-info \%D/... \%D/info/dir\" 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 \%D/... \%D/info/dir\" 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 { 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: 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 { 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-Za-z0-9]+)[:\$]/) { &perror("WARN: $file includes possible RCS tag \"\$$1\$\". ". "use binary mode (-ko) on commit/import."); } 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 $lang_pref = ''; my $tmp; my $bogusdistfiles = 0; my @varnames = (); my($portname, $portversion, $distfiles, $distname, $extractsufx) = ('', '', '', '', ''); my $masterport = 0; my $slaveport = 0; my($realwrksrc, $wrksrc, $nowrksubdir) = ('', '', ''); my(@mman, @pman); 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; print "OK: checking contiguous blank lines in $file.\n" if ($verbose); $i = "\n" x ($contblank + 2); if ($whole =~ /$i/) { my @linesbefore = split(/\n/, $`); &perror("FATAL: contiguous blank lines (> $contblank lines) found ". "in $file at line " . ($#linesbefore + 1) . "."); } # # 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: NO_CHECKSUM # $whole =~ s/\n#[^\n]*/\n/g; $whole =~ s/\n\n+/\n/g; print "OK: checking NO_CHECKSUM.\n" if ($verbose); if ($whole =~ /\nNO_CHECKSUM/) { &perror("FATAL: use of NO_CHECKSUM discouraged. ". "it is intended to be a user variable."); } # # whole file: PKGNAME # print "OK: checking PKGNAME.\n" if ($verbose); if ($whole =~ /\nPKGNAME.?=/) { &perror("FATAL: 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)\)/) { &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."); } # # 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 cat chmod chown cp echo expr false gmake grep gzcat ldconfig ln md5 mkdir mv patch rm rmdir sed sh touch tr which xmkmf )) { $cmdnames{$i} = "\$\{\U$i\E\}"; } $cmdnames{'env'} = '${SETENV}'; $cmdnames{'gunzip'} = '${GUNZIP_CMD}'; $cmdnames{'gzip'} = '${GZIP_CMD}'; $cmdnames{'install'} = '${INSTALL_foobaa}'; # # 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][\@-]?)(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+("(\\'|\\"|[^"])*"|'(\\'|\\"|[^'])*')[ \t]*[;\n]/$1$2;/; #" foreach my $i (keys %cmdnames) { if ($j =~ /[ \t\/]$i[ \t\n;]/ && $j !~ /\n[A-Z]+_TARGET[?+]?=[^\n]+$i/) { &perror("WARN: possible direct use of command \"$i\" ". "found. use $cmdnames{$i} instead."); } } # # whole file: ldconfig must come with "true" command # if ($ldconfigwithtrue && $j =~ /(ldconfig|\$[{(]LDCONFIG[)}])/ && $j !~ /(\/usr\/bin\/true|\$[{(]TRUE[)}])/) { &perror("FATAL: ldconfig must be used with \"||\${TRUE}\"."); } # # whole file: ${GZIP_CMD} -9 (or any other number) # if ($j =~ /\${GZIP_CMD}\s+-(\w+(\s+-)?)*(\d)/) { &perror("WARN: 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/) { &perror("WARN: possible use of \"\${MKDIR} -p\" ". "found. \${MKDIR} includes \"-p\" by default."); } # # whole file: full path name # &abspathname($whole, $file); # # slave port check # my $masterdir = $makevar{MASTERDIR}; if ($masterdir ne '' && $masterdir ne $makevar{'.CURDIR'}) { $slaveport = 1; print "OK: checking master port in $masterdir.\n" if ($verbose); if (! -e "$masterdir/Makefile") { &perror("WARN: unable to locate master port in $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", < # # $FreeBSD$ # # This port is self contained in the src directory. # PORTNAME= portlint -PORTVERSION= 2.3.1 +PORTVERSION= 2.3.2 CATEGORIES= devel MASTER_SITES= # none DISTFILES= # none MAINTAINER= mharo@FreeBSD.org NO_BUILD= yes NO_WRKSUBDIR= yes USE_PERL5= yes SRC= ${.CURDIR}/src MAN1= portlint.1 do-fetch: @${DO_NADA} do-install: ${INSTALL_SCRIPT} ${SRC}/portlint.pl ${PREFIX}/bin/portlint ${INSTALL_MAN} ${SRC}/portlint.1 ${MAN1PREFIX}/man/man1 .include Property changes on: head/ports-mgmt/portlint/Makefile ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.43 \ No newline at end of property +1.44 \ No newline at end of property Index: head/ports-mgmt/portlint/src/portlint.pl =================================================================== --- head/ports-mgmt/portlint/src/portlint.pl (revision 40121) +++ head/ports-mgmt/portlint/src/portlint.pl (revision 40122) @@ -1,1655 +1,1686 @@ #! /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.28.2.1 2000/04/24 02:12:36 mharo Exp $ # use vars qw/ $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 = 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 $manchapters = '123456789ln'; my $localbase = '/usr/local'; my %lang_pref = qw( chinese zh french fr german de hebrew iw japanese ja korean ko russian ru vietnamese vi ); my @lang_cat = keys %lang_pref; my @lang_pref = values %lang_pref; my $re_lang_pref = '(' . join('|', @lang_pref) . ')-'; my ($prog) = ($0 =~ /([^\/]+)$/); sub usage { print STDERR <); close(MK); my $cmd = join(' -V MASTER_SITE_', "make $makeenv -f - all", @site_groups); my $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{COMMENT}, $makevar{DESCR}, 'Makefile', $makevar{MD5_FILE}); my %checker = ( $makevar{COMMENT} => 'checkdescr', $makevar{DESCR} => 'checkdescr', 'Makefile' => 'checkmakefile', $makevar{MD5_FILE} => 'TRUE' ); if ($extrafile) { my @files = ( <$makevar{SCRIPTDIR}/*>, @makevar{COMMENT,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 (! -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 ($i !~ m@/files/patch-@) { + if ($proc ne 'checkpatch') { &checklastline($i) || &perror("Cannot open the file $i\n"); } } } if ($committer) { - if (scalar(@_ = ) || -d "work") { - &perror("FATAL: be sure to cleanup $portdir/work ". - "before committing the port."); + 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."); + $problem = 1; + } elsif (-d && scalar(@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; + } } - if (scalar(@_ = <*/*~>) || scalar(@_ = <*~>)) { - &perror("FATAL: for safety, be sure to cleanup ". - "editor backup files before committing the port."); - } - if (scalar(@_ = <*/*.orig>) || scalar(@_ = ) - || scalar(@_ = <*/*.rej>) || scalar(@_ = <*.rej>)) { - &perror("FATAL: for safety, be sure to cleanup ". - "patch backup files before committing the port."); - } + + find(\&find_proc, '.'); } 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 { my($file) = @_; my(%maxchars) = ($makevar{COMMENT}, 70, $makevar{DESCR}, 80); my(%maxlines) = ($makevar{COMMENT}, 1, $makevar{DESCR}, 24); my(%errmsg) = ($makevar{COMMENT}, "must be one-liner.", $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. $file should be ". "plain ascii file."); } 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:"); } } if ($file =~ /\bpkg-comment/) { if (($tmp !~ /^["0-9A-Z]/) || ($tmp =~ m/\.$/)) { #" &perror("WARN: pkg-comment should begin with a capital, and end without a period"); } } close(IN); } # # pkg-plist # sub checkplist { my($file) = @_; my($curdir) = ($localbase); my($inforemoveseen, $infoinstallseen, $infoseen) = (0, 0, 0); my($infobeforeremove, $infoafterinstall) = (0, 0); my($infooverwrite) = (0); my($rcsidseen) = (0); my(@exec_info) = (); my(@unexec_info) = (); my(@infofile) = (); 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 ($_ =~ /^\@/) { if ($_ =~ /^\@(cwd|cd)[ \t]+(\S+)/) { $curdir = $2; } elsif ($_ =~ /^\@unexec[ \t]+rmdir/) { if ($_ !~ /true$/) { &perror("WARN: use \"\@dirrm\" ". "instead of \"\@unexec rmdir\"."); } } elsif ($_ =~ /^\@exec[ \t]+install-info\s+(.+)\s+(.+)$/) { $infoinstallseen = $.; push(@exec_info, $1); } elsif ($_ =~ /^\@unexec[ \t]+install-info[ \t]+--delete\s+(.+)\s+(.+)$/) { $inforemoveseen = $.; push(@unexec_info, $1); } 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: \@$1 should not be needed in pkg-plist"); } 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$/) { &perror("WARN: $file $.: installing libtool archives, ". "please use USE_LIBTOOL in Makefile if possible"); } if ($_ =~ /\.so(\.\d+)?$/ && $makevar{INSTALLS_SHLIB} eq '') { &perror("WARN: $file $.: installing shared libraries, ". "please define INSTALLS_SHLIB as appropriate"); } if ($_ =~ /^info\/.*info(-[0-9]+)?$/) { $infoseen = $.; $infoafterinstall++ if ($infoinstallseen); $infobeforeremove++ if (!$inforemoveseen); push(@infofile, $_); } 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)?$#) { 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++; } } # 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: you need an '\@exec install-info \%D/$if \%D/info/dir' line in your pkg-plist"); } if ($unexec_install !~ m/\%D\/$if/) { &perror("FATAL: you need an '\@unexec install-info --delete \%D/$if \%D/info/dir' line in your pkg-plist"); } } 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: install-info must be used to ". "add/delete entries into \"info/dir\"."); } &perror("FATAL: \"\@exec install-info \%D/... \%D/info/dir\" 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 \%D/... \%D/info/dir\" 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 { 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: 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 { 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-Za-z0-9]+)[:\$]/) { &perror("WARN: $file includes possible RCS tag \"\$$1\$\". ". "use binary mode (-ko) on commit/import."); } 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 $lang_pref = ''; my $tmp; my $bogusdistfiles = 0; my @varnames = (); my($portname, $portversion, $distfiles, $distname, $extractsufx) = ('', '', '', '', ''); my $masterport = 0; my $slaveport = 0; my($realwrksrc, $wrksrc, $nowrksubdir) = ('', '', ''); my(@mman, @pman); 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; print "OK: checking contiguous blank lines in $file.\n" if ($verbose); $i = "\n" x ($contblank + 2); if ($whole =~ /$i/) { my @linesbefore = split(/\n/, $`); &perror("FATAL: contiguous blank lines (> $contblank lines) found ". "in $file at line " . ($#linesbefore + 1) . "."); } # # 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: NO_CHECKSUM # $whole =~ s/\n#[^\n]*/\n/g; $whole =~ s/\n\n+/\n/g; print "OK: checking NO_CHECKSUM.\n" if ($verbose); if ($whole =~ /\nNO_CHECKSUM/) { &perror("FATAL: use of NO_CHECKSUM discouraged. ". "it is intended to be a user variable."); } # # whole file: PKGNAME # print "OK: checking PKGNAME.\n" if ($verbose); if ($whole =~ /\nPKGNAME.?=/) { &perror("FATAL: 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)\)/) { &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."); } # # 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 cat chmod chown cp echo expr false gmake grep gzcat ldconfig ln md5 mkdir mv patch rm rmdir sed sh touch tr which xmkmf )) { $cmdnames{$i} = "\$\{\U$i\E\}"; } $cmdnames{'env'} = '${SETENV}'; $cmdnames{'gunzip'} = '${GUNZIP_CMD}'; $cmdnames{'gzip'} = '${GZIP_CMD}'; $cmdnames{'install'} = '${INSTALL_foobaa}'; # # 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][\@-]?)(echo|\$[\{\(]ECHO[\}\)]|\$[\{\(]ECHO_MSG[\}\)])[ \t]+("(\\'|\\"|[^"])*"|'(\\'|\\"|[^'])*')[ \t]*[;\n]/$1$2;/; #" foreach my $i (keys %cmdnames) { if ($j =~ /[ \t\/]$i[ \t\n;]/ && $j !~ /\n[A-Z]+_TARGET[?+]?=[^\n]+$i/) { &perror("WARN: possible direct use of command \"$i\" ". "found. use $cmdnames{$i} instead."); } } # # whole file: ldconfig must come with "true" command # if ($ldconfigwithtrue && $j =~ /(ldconfig|\$[{(]LDCONFIG[)}])/ && $j !~ /(\/usr\/bin\/true|\$[{(]TRUE[)}])/) { &perror("FATAL: ldconfig must be used with \"||\${TRUE}\"."); } # # whole file: ${GZIP_CMD} -9 (or any other number) # if ($j =~ /\${GZIP_CMD}\s+-(\w+(\s+-)?)*(\d)/) { &perror("WARN: 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/) { &perror("WARN: possible use of \"\${MKDIR} -p\" ". "found. \${MKDIR} includes \"-p\" by default."); } # # whole file: full path name # &abspathname($whole, $file); # # slave port check # my $masterdir = $makevar{MASTERDIR}; if ($masterdir ne '' && $masterdir ne $makevar{'.CURDIR'}) { $slaveport = 1; print "OK: checking master port in $masterdir.\n" if ($verbose); if (! -e "$masterdir/Makefile") { &perror("WARN: unable to locate master port in $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", <