diff options
author | itojun <itojun@FreeBSD.org> | 1999-01-05 20:23:28 +0800 |
---|---|---|
committer | itojun <itojun@FreeBSD.org> | 1999-01-05 20:23:28 +0800 |
commit | 179d08ec99aea611d8e60d5a2633ddfea03b6462 (patch) | |
tree | 543fcd46959d072d8d38f8119bed5b31b4c313fb | |
parent | 93cb564416799a3a6c649c969be12103086566fe (diff) | |
download | freebsd-ports-gnome-179d08ec99aea611d8e60d5a2633ddfea03b6462.tar.gz freebsd-ports-gnome-179d08ec99aea611d8e60d5a2633ddfea03b6462.tar.zst freebsd-ports-gnome-179d08ec99aea611d8e60d5a2633ddfea03b6462.zip |
being tired of maintaining portlint only by myself, I decided to
put the source code for "portlint" into FreeBSD port tree. The
imported revision is 1.65 (in my local RCS file). (pseudo) version
number for the port is now 2.0.
Whoever make changes to bsd.port.mk is advised to update portlint.pl
too :-)
Note that portlint.pl MUST be portable enough to handle (Net|Open|Free)BSD
bsd.port.mk. There are people using portlint.pl on non-FreeBSD
platforms.
-rw-r--r-- | devel/portlint/Makefile | 23 | ||||
-rw-r--r-- | devel/portlint/src/portlint.1 | 120 | ||||
-rw-r--r-- | devel/portlint/src/portlint.pl | 1342 | ||||
-rw-r--r-- | ports-mgmt/portlint/Makefile | 23 | ||||
-rw-r--r-- | ports-mgmt/portlint/src/portlint.1 | 120 | ||||
-rw-r--r-- | ports-mgmt/portlint/src/portlint.pl | 1342 |
6 files changed, 2948 insertions, 22 deletions
diff --git a/devel/portlint/Makefile b/devel/portlint/Makefile index 9392ace79cae..22227e56de21 100644 --- a/devel/portlint/Makefile +++ b/devel/portlint/Makefile @@ -1,28 +1,29 @@ # New ports collection makefile for: portlint -# Version required: 1.61 +# Version required: (self contained) # Date created: 13 Jun 1997 # Whom: Jun-ichiro itojun Itoh <itojun@itojun.org> # -# $Id: Makefile,v 1.19 1997/11/25 11:43:15 itojun Exp $ +# $Id: Makefile,v 1.20 1998/06/27 05:18:50 asami Exp $ # -DISTNAME= portlint-1.61 +DISTNAME= portlint-2.0 CATEGORIES= devel -MASTER_SITES= ftp://ftp.foretune.co.jp/pub/tools/portlint/ -EXTRACT_SUFX= .shar MAINTAINER= itojun@itojun.org -EXTRACT_CMD= ${CAT} -EXTRACT_BEFORE_ARGS= # yes, it is blank -EXTRACT_AFTER_ARGS= |sh -NO_WRKSUBDIR= yes +NO_WRKDIR= yes +NO_EXTRACT= yes NO_BUILD= yes +SRCDIR= ${.CURDIR}/src + MAN1= portlint.1 +do-fetch: + @${DO_NADA} + do-install: - ${INSTALL_SCRIPT} ${WRKDIR}/portlint.pl ${PREFIX}/bin/portlint - ${INSTALL_MAN} ${WRKDIR}/portlint.1 ${MAN1PREFIX}/man/man1 + ${INSTALL_SCRIPT} ${WRKSRC}/portlint.pl ${PREFIX}/bin/portlint + ${INSTALL_MAN} ${WRKSRC}/portlint.1 ${MAN1PREFIX}/man/man1 .include <bsd.port.mk> diff --git a/devel/portlint/src/portlint.1 b/devel/portlint/src/portlint.1 new file mode 100644 index 000000000000..72d13a183457 --- /dev/null +++ b/devel/portlint/src/portlint.1 @@ -0,0 +1,120 @@ +.\" $Id: portlint.1,v 1.8 1997/11/25 14:53:14 itojun Exp $ +.\" +.\" Copyright (c) 1997 by Jun-ichiro Itoh <itojun@itojun.org>. +.\" All Rights Reserved. Absolutely no warranty. +.\" +.Dd July 11, 1997 +.Dt PORTLINT 1 +.Sh NAME +.Nm portlint +.Nd a verifier for port directory +.Sh SYNOPSIS +.Nm portlint +.Op Fl abchvN +.Op Fl B Ar n +.Op Ar dir +.Sh DESCRIPTION +.Nm +tries to verify the content of a port directory. +The purpose of +.Nm +can be separated into two parts: +.Pq 1 +to let the submitters easily polish her/his own port directory, and +.Pq 2 +to decrease the labor of the committers. +.Pp +.Nm +uses very simple regular-expression matching for verifying +files that make up a port directory. +Note that it does NOT implement complete parser for those files. +Because of this the user may see some extra warnings, +especially when checking complex +.Pa Makefile Ns No s . +.Pp +.Sy Options +.Bl -tag -width Fl +.It Fl a +Perform additional checks for extra files, such as +.Pa scripts/* +and +.Pa pkg/* . +.It Fl b +Warn the use of +.Pa $(VARIABLE) . +Some of the committers prefer +.Pa ${VARIABLE} +instead of +.Pa $(VARIABLE) , +even though they are semantically same. +.It Fl c +Committer flag. +It will add several checks useful only for committers. +If you are a committer and performing check just before commiting a port, +use this option. +.It Fl h +Show the summary of command line options, then exit. +.It Fl v +Be verbose. +Show the progress report for items that are being checked. +.It Fl N +New port flag. +Adds several checks specific to newly submitted port. +If you are willing to submit the directory to be checked as a new port, +use this option. +.It Fl B Ar n +Set the number of contiguous blank lines allowed in +.Pa Makefile +to +.Ar n . +(by default, +.Ar n +is 1) +.It dir +The port directory to be checked. +If omitted, check will be performed over the current directory. +.El +.Sh DIAGNOSTICS +Messages will be sent to standard output, not standard error output. +.Bl -tag -width WARN: foobaa +.It FATAL: ... +This type of error messages suggest that there is some fatal error +in the port directory. +For example, if some files need a rewrite, or if +some inevitable files are missing, this message will show up. +This kind of errors should be avoided BEFORE submitting +a port via send-pr to the comitters. +.\"If a submitter submit it without update, committers will need to rewrite +.\"on behalf of the submitters, which may result in delay of +.\"the development of operating system itself. +.It WARN: ... +This type of error messages suggest that some files may (or may not) +need some fix. +Basically, warnings are produced when +.Nm +is not completely sure about the result. +For example, complex +.Pa Makefile Ns No s +may need some statements that can match the regular expression +.Nm +uses for sanity checks. +In those cases, the user should evaluate the result manually, +and obey/ignore the result. +.It OK: ... +This type of messages are used in verbose mode +.Pq Fl v . +.El +.Sh FILES +.Bl -tag -width /usr/share/mk/bsd.port.mk -compact +.It Pa /usr/share/mk/bsd.port.mk +master Makefile for ports +.It Pa /usr/ports/* +port collection +.Sh AUTHORS +Jun-ichiro Itoh <itojun@itojun.org> +and +Yoshishige Arai <ryo2@on.rim.or.jp>. +Many people has contributed patches and comments/suggestions. +.Sh BUGS +.Nm +is not a magic wand, as described above. diff --git a/devel/portlint/src/portlint.pl b/devel/portlint/src/portlint.pl new file mode 100644 index 000000000000..6ab44b4bf681 --- /dev/null +++ b/devel/portlint/src/portlint.pl @@ -0,0 +1,1342 @@ +#! /usr/bin/perl +# +# portlint - lint for port directory +# implemented by: +# Jun-ichiro itojun Itoh <itojun@itojun.org> +# Yoshishige Arai <ryo2@on.rim.or.jp> +# visit ftp://ftp.foretune.co.jp/pub/tools/portlint/ for latest version. +# +# Copyright(c) 1997 by Jun-ichiro Itoh <itojun@itojun.org>. +# All rights reserved. +# Freely redistributable. Absolutely no warranty. +# +# Pleae note that this perl code MUST 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... +# +# $Id: portlint.pl,v 1.65 1998/03/07 04:17:55 itojun Exp $ +# + +$err = $warn = 0; +$extrafile = $parenwarn = $committer = $verbose = $newport = 0; +$contblank = 1; +$portdir = '.'; + +# default setting - for FreeBSD +$portsdir = '/usr/ports'; +$rcsidstr = '(Id|FreeBSD)'; +$multiplist = 0; +$ldconfigwithtrue = 0; +$rcsidinplist = 0; +$mancompress = 1; +$manstrict = 0; +$newxdef = 1; +$automan = 1; +$manchapters = '123456789ln'; +$localbase = '/usr/local'; + +#select(STDERR); +while (@ARGV > 0) { + $_ = shift; + /^-h/ && do { + ($prog) = ($0 =~ /([^\/]+)$/); + print STDERR <<EOF; +usage: $prog [-abcvN] [-B#] [port_directory] + -a additional check for scripts/* and pkg/* + -b warn \$(VARIABLE) + -c committer mode + -v verbose mode + -N writing a new port + -B# allow # contiguous blank lines (default: $contblank line) +EOF + exit 0; + }; + /^-a/ && do {$extrafile = 1; next;}; + /^-b/ && do {$parenwarn = 1; next;}; + /^-c/ && do {$committer = 1; next;}; + /^-v/ && do {$verbose = 1; next;}; + /^-N/ && do {$newport = 1; next;}; + /^-B(\d+)$/ && do { $contblank = $1; next; }; + @ARGV > 0 && /^-B$/ && do { + $contblank = shift; + if ($contblank !~ /^\d+$/) { + print STDERR "FATAL: -B must come with number.\n"; + exit 1; + } + next; + }; + $portdir = $_; +} + +# OS dependent configs +# os portsdir rcsid mplist ldcfg plist-rcsid mancompresss strict localbase newxdef automan +@osdep = split(/\n/, <<EOF); +FreeBSD /usr/ports (Id|FreeBSD) 0 0 0 1 0 /usr/local 1 1 +NetBSD /usr/pkgsrc NetBSD 1 1 1 0 1 /usr/pkg 0 0 +EOF +$osname = `uname -s`; +$osname =~ s/\n$//; +foreach $i (@osdep) { + if ($i =~ /^$osname\t(.*)/) { + print "OK: found OS config for $osname.\n" if ($verbose); + ($portsdir, $rcsidstr, $multiplist, $ldconfigwithtrue, + $rcsidinplist, $mancompress, $manstrict, $localbase, + $newxdef, $automan) + = split(/\t+/, $1); + last; + } +} +if ($verbose) { + print "OK: config: portsdir: \"$portsdir\" ". + "rcsidstr: \"$rcsidstr\" ". + "multiplist: $multiplist ". + "ldconfigwithtrue: $ldconfigwithtrue ". + "rcsidinplist: $rcsidinplist ". + "mancompress: $mancompress ". + "manstrict: $manstrict ". + "localbase: $localbase ". + "\n"; +} + +# +# just for safety. +# +if (! -d $portdir) { + print STDERR "FATAL: invalid directory $portdir specified.\n"; + exit 1; +} + +# +# variables for global checks. +# +$sharedocused = 0; +%plistmanall = (); +%plistmangz = (); +%plistman = (); +%manlangs = (); + +%predefined = (); +foreach $i (split("\n", <<EOF)) { +XCONTRIB ftp://ftp.x.org/contrib/ +XCONTRIB ftp://crl.dec.com/pub/X11/contrib/ +GNU ftp://prep.ai.mit.edu/pub/gnu/ +GNU ftp://wuarchive.wustl.edu/systems/gnu/ +PERL_CPAN ftp://ftp.digital.com/pub/plan/perl/CPAN/modules/by-module/ +PERL_CPAN ftp://ftp.cdrom.com/pub/perl/CPAN/modules/by-module/ +TEX_CTAN ftp://ftp.cdrom.com/pub/tex/ctan/ +TEX_CTAN ftp://wuarchive.wustl.edu/packages/TeX/ +TEX_CTAN ftp://ftp.funet.fi/pub/TeX/CTAN/ +TEX_CTAN ftp://ftp.tex.ac.uk/public/ctan/tex-archive/ +TEX_CTAN ftp://ftp.dante.de/tex-archive/ +SUNSITE ftp://sunsite.unc.edu/pub/Linux/ +SUNSITE ftp://ftp.infomagic.com/pub/mirrors/linux/sunsite/ +SUNSITE ftp://ftp.funet.fi/pub/mirrors/sunsite.unc.edu/pub/Linux/ +EOF + ($j, $k) = split(/\t+/, $i); + $predefined{$k} = $j; +} + +# +# check for files. +# +@checker = ('pkg/COMMENT', 'pkg/DESCR', 'Makefile', 'files/md5'); +%checker = ('pkg/COMMENT', 'checkdescr', 'pkg/DESCR', 'checkdescr', + 'Makefile', 'checkmakefile', 'files/md5', 'TRUE'); +if ($extrafile) { + foreach $i ((<$portdir/scripts/*>, <$portdir/pkg/*>)) { + next if (! -T $i); + $i =~ s/^$portdir\///; + 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/^$portdir\///; + next if (defined $checker{$i}); + push(@checker, $i); + $checker{$i} = 'checkpatch'; +} +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 ($committer) { + if (scalar(@_ = <$portdir/work/*>) || -d "$portdir/work") { + &perror("WARN: be sure to cleanup $portdir/work ". + "before committing the port."); + } + if (scalar(@_ = <$portdir/*/*~>) || scalar(@_ = <$portdir/*~>)) { + &perror("WARN: for safety, be sure to cleanup ". + "emacs backup files before committing the port."); + } + 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 port."); + } +} +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 (<IN>) { + $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: $i includes lines that exceed $maxchars{$file} ". + "charactors."); + } + if ($tmp =~ /[\033\200-\377]/) { + &perror("WARN: pkg/DESCR includes iso-8859-1, or ". + "other local characters. $file should be". + "plain ascii file."); + } + close(IN); +} + +# +# 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 (<IN>) { + 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 + } 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)?$#) { + 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: 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 (<IN>) { + $whole .= $_; + } + &abspathname($whole, $file); + close(IN); +} + +sub checklastline { + local($file) = @_; + local($whole); + + open(IN, "< $portdir/$file") || return 0; + $whole = ''; + while (<IN>) { + $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 unnecessery blank lines ". + "at the last part."); + } + + close(IN); +} + +sub checkpatch { + local($file) = @_; + local($whole); + + if (-z "$portdir/$file") { + &perror("FATAL: $file has no content. should be removed ". + "from repository."); + return; + } + + open(IN, "< $portdir/$file") || return 0; + $whole = ''; + while (<IN>) { + $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 { + 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 (<IN>) { + 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."); + } + + # + # whole file: direct use of command names + # + print "OK: checking direct use of command names.\n" if ($verbose); + foreach $i (split(/\s+/, <<EOF)) { +awk basename cat cp echo false gmake grep gzcat +ldconfig md5 mkdir mv patch rm rmdir sed setenv touch tr xmkmf +EOF + $cmdnames{$i} = "\$\{\U$i\E\}"; + } + $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 $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: ${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); + + # + # break the makefile into sections. + # + @sections = split(/\n\n+/, $rawwhole); + for ($i = 0; $i < scalar(@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); + @linestocheck = split("\n", <<EOF); +Whom +Version [rR]equired +Date [cC]reated +EOF + if ($osname eq 'NetBSD') { + unshift(@linestocheck, '(New )?[pP](ackage|ort)s [cC]ollection [mM]akefile [fF]or'); + } else { + unshift(@linestocheck, '(New )?[pP]orts [cC]ollection [mM]akefile [fF]or'); + } + $tmp = $sections[$idx++]; + $tmp = "\n" . $tmp; # to make the begin-of-line check easier + + if ($tmp =~ /\n[^#]/) { + &perror("FATAL: non-comment line in comment section of $file."); + } + foreach $i (@linestocheck) { + $j = $i; + $j =~ s/\(.*\)\?//g; + $j =~ s/\[(.)[^\]]*\]/$1/g; + if ($tmp !~ /# $i:[ \t]+\S+/) { + &perror("FATAL: no \"$j\" line in ". + "comment section of $file."); + } else { + print "OK: \"$j\" seen in $file.\n" if ($verbose); + } + } + if ($tmp !~ /#\n#(\s+)\$$rcsidstr([^\$]*)\$\n/) { + &perror("FATAL: no \$$rcsidstr\$ line in $file comment ". + "section."); + } else { + print "OK: \$$rcsidstr\$ seen in $file.\n" if ($verbose); + if ($1 ne ' ') { + &perror("WARN: please use single whitespace ". + "right before \$$rcsidstr\$ tag."); + } + if ($2 ne '') { + if ($verbose || $newport) { # XXX + &perror("WARN: ". + ($newport ? 'for new port, ' + : 'is it a new port? if so, '). + "make \$$rcsidstr\$ tag in comment ". + "section empty, to make CVS happy."); + } + } + } + + # + # for the rest of the checks, comment lines are not important. + # + for ($i = 0; $i < scalar(@sections); $i++) { + $sections[$i] =~ s/\n#[^\n]*//g; + $sections[$i] =~ s/\n\n+/\n/g; + $sections[$i] =~ s/\\\n/ /g; + } + + # + # + # section 2: DISTNAME/PKGNAME/... + # + print "OK: checking first section of $file. (DISTNAME/...)\n" + if ($verbose); + $tmp = $sections[$idx++]; + + # check the order of items. + &checkorder('DISTNAME', $tmp, split(/\s+/, <<EOF)); +DISTNAME PKGNAME CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR +EXTRACT_SUFX DISTFILES +EOF + + # check the items that has to be there. + $tmp = "\n" . $tmp; + foreach $i ('DISTNAME', 'CATEGORIES') { + print "OK: checking $i.\n" if ($verbose); + if ($tmp !~ /\n$i=/) { + &perror("FATAL: $i has to be there."); + } + if ($tmp =~ /\n$i(\?=)/) { + &perror("FATAL: $i has to be set by \"=\", ". + "not by \"$1\"."); + } + } + + # check x11 in CATEGORIES + if ($newxdef + && $tmp =~ /\nCATEGORIES[+?]?=[ \t]*([^\n]*)\n/ && $1 =~ /x11/) { + print "OK: checking x11 in CATEGORIES.\n" if ($verbose); + @i = split(/\s+/, $1); + if ($i[0] eq 'x11') { + ; # okay + } elsif ($i[0] =~ /(chinese|japanese|korean|german|russian)/) { + ; # okay + } else { + &perror("WARN: only specific kind of apps should ". + "specify \"x11\" in CATEGORIES. ". + "do you mean just USE_XLIB? ". + "then remove \"x11\" from CATEGORIES."); + } + } + + # check the URL + if ($tmp =~ /\nMASTER_SITES[+?]?=[ \t]*([^\n]*)\n/ + && $1 !~ /^[ \t]*$/) { + print "OK: seen MASTER_SITES, sanity checking URLs.\n" + if ($verbose); + @sites = split(/\s+/, $1); + foreach $i (@sites) { + if ($i =~ m#^\w+://#) { + if ($i !~ m#/$#) { + &perror("FATAL: URL \"$i\" should ". + "end with \"/\"."); + } + if ($i =~ m#://[^/]*:/#) { + &perror("FATAL: URL \"$i\" contains ". + "extra \":\"."); + } + unless (&is_predefined($i)) { + print "OK: URL \"$i\" ok.\n" + if ($verbose); + } + } else { + print "OK: non-URL \"$i\" ok.\n" + if ($verbose); + } + } + } else { + &perror("WARN: no MASTER_SITES found. is it ok?"); + } + + # check DISTFILES and related items. + $distfiles = $1 if ($tmp =~ /\nDISTFILES[+?]?=[ \t]*([^\n]+)\n/); + $pkgname = $1 if ($tmp =~ /\nPKGNAME[+?]?=[ \t]*([^\n]+)\n/); + $distname = $1 if ($tmp =~ /\nDISTNAME[+?]?=[ \t]*([^\n]+)\n/); + $extractsufx = $1 if ($tmp =~ /\nEXTRACT_SUFX[+?]?=[ \t]*([^\n]+)\n/); + + # check bogus EXTRACT_SUFX. + if ($extractsufx ne '') { + print "OK: seen EXTRACT_SUFX, checking value.\n" if ($verbose); + if ($distfiles ne '') { + &perror("WARN: no need to define EXTRACT_SUFX if ". + "DISTFILES is defined."); + } + if ($extractsufx eq '.tar.gz') { + &perror("WARN: EXTRACT_SUFX is \".tar.gz.\" ". + "by default. you don't need to specify it."); + } + } else { + print "OK: no EXTRACT_SUFX seen, using default value.\n" + if ($verbose); + $extractsufx = '.tar.gz'; + } + + print "OK: sanity checking PKGNAME.\n" if ($verbose); + if ($pkgname ne '' && $pkgname eq $distname) { + &perror("WARN: PKGNAME is \${DISTNAME} by default, ". + "you don't need to define PKGNAME."); + } + $i = ($pkgname eq '') ? $distname : $pkgname; + if ($i =~ /-([^-]+)$/) { + $j = $`; + $k = $1; + if ($j =~ /[0-9]$/) { + &perror("WARN: is \"$j\" sane as package name ". + "WITHOUT version number? ". + "if not, avoid \"-\" in version number ". + "part of ". + (($pkgname eq '') ? "DISTNAME." : "PKGNAME.")); + } + if ($k =~ /^pl[0-9]*$/ + || $k =~ /^[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9]*)*$/) { + print "OK: trailing part of PKGNAME\"-$k\" ". + "looks fine.\n" if ($verbose); + } else { + &perror("FATAL: version number part of PKGNAME". + (($pkgname eq '') + ? ', which is derived from DISTNAME, ' + : ' '). + "looks illegal. should modify \"-$k\"."); + } + } else { + &perror("FATAL: PKGNAME". + (($pkgname eq '') + ? ', which is derived from DISTNAME, ' + : ' '). + "must come with version number, like \"foobaa-1.0\"."); + if ($i =~ /_pl[0-9]*$/ + || $i =~ /_[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9]*)*$/) { + &perror("FATAL: you seem to using underline ". + "before version number in PKGNAME. ". + "it has to be hyphen."); + } + } + + # 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+$/) { + $bogusdistfiles++; + print "OK: seen DISTFILES with single item, checking value.\n" + if ($verbose); + &perror("WARN: use of DISTFILES with single file ". + "discouraged. distribution filename should be set by ". + "DISTNAME and EXTRACT_SUFX."); + if ($distfiles eq $distname . $extractsufx) { + &perror("WARN: definition of DISTFILES not necessery. ". + "DISTFILES is \${DISTNAME}/\${EXTRACT_SUFX} ". + "by default."); + } + + # make an advice only in certain cases. + if ($pkgname ne '' && $distfiles =~ /^$pkgname([-\.].+)$/) { + &perror("WARN: how about \"DISTNAME=$pkgname\"". + (($1 eq '.tar.gz') + ? "" + : " and \"EXTRACT_SUFX=$1\""). + ", instead of DISTFILES?"); + } + } + + # additional checks for committer. + $i = ($pkgname eq '') ? $distname : $pkgname; + if ($committer && $i =~ /^(de|ja|ko|ru|vi|zh)-/) { + &perror("WARN: be sure to include country code \"$1-\" ". + "in the module alias name."); + } + if ($committer && -f "$portdir/$i.tgz") { + &perror("WARN: be sure to remove $portdir/$i.tgz ". + "before committing the port."); + } + + push(@varnames, split(/\s+/, <<EOF)); +DISTNAME PKGNAME CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR +EXTRACT_SUFX DISTFILES +EOF + + # + # section 3: PATCH_SITES/PATCHFILES(optional) + # + print "OK: checking second section of $file, (PATCH*: optinal).\n" + if ($verbose); + $tmp = $sections[$idx]; + + if ($tmp =~ /(PATCH_SITES|PATCH_SITE_SUBDIR|PATCHFILES|PATCH_DIST_STRIP)/) { + &checkearlier($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'); + + $idx++; + } + + push(@varnames, split(/\s+/, <<EOF)); +PATCH_SITES PATCHFILES PATCH_DIST_STRIP +EOF + + # + # section 4: MAINTAINER + # + print "OK: checking third section of $file (MAINTAINER).\n" + if ($verbose); + $tmp = $sections[$idx++]; + + &checkearlier($tmp, @varnames); + $tmp = "\n" . $tmp; + if ($tmp =~ /\nMAINTAINER=[^\n]+/) { + $tmp =~ s/\nMAINTAINER=[^\n]+//; + } else { + &perror("FATAL: no MAINTAINER listed in $file."); + } + $tmp =~ s/\n\n+/\n/g; + + &checkextra($tmp, 'MAINTAINER'); + + push(@varnames, 'MAINTAINER'); + + # + # 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 = split(/\s+/, <<EOF); +LIB_DEPENDS BUILD_DEPENDS RUN_DEPENDS FETCH_DEPENDS DEPENDS DEPENDS_TARGET +EOF + if ($tmp =~ /(LIB_|BUILD_|RUN_|FETCH_)?DEPENDS/) { + &checkearlier($tmp, @varnames); + + if (!defined $ENV{'PORTSDIR'}) { + $ENV{'PORTSDIR'} = $portsdir; + } + foreach $i (grep(/^[A-Z_]*DEPENDS[?+]?=/, split(/\n/, $tmp))) { + $i =~ s/^([A-Z_]*DEPENDS)[?+]?=[ \t]*//; + $j = $1; + print "OK: checking ports listed in $j.\n" + if ($verbose); + foreach $k (split(/\s+/, $i)) { + @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: wrong dependency value ". + "for $j. $j requires ". + ($j eq 'DEPENDS' + ? "1 or 2 " + : "2 or 3 "). + "colon-separated tuples."); + next; + } + %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: dependency to perl5 ". + "listed in $j. consider using ". + "USE_PERL5."); + } + + # check USE_GMAKE + if ($m{'dep'} =~ /^(gmake|\${GMAKE})$/) { + &perror("WARN: dependency to $1 ". + "listed in $j. consider using ". + "USE_GMAKE."); + } + + # check USE_QT + if ($m{'dep'} =~ /^(qt\d)+$/) { + &perror("WARN: dependency to $1 ". + "listed in $j. consider using ". + "USE_QT."); + } + + # check backslash in LIB_DEPENDS + if ($osname eq 'NetBSD' && $j eq 'LIB_DEPENDS' + && $m{'dep'} =~ /\\\\./) { + &perror("WARN: use of backslashes in ". + "$j is deprecated."); + } + + # check port dir existence + $k = $m{'dir'}; + $k =~ s/\${PORTSDIR}/$ENV{'PORTSDIR'}/; + if (! -d $k) { + &perror("WARN: no port directory $k ". + "found, even though it is ". + "listed in $j."); + } else { + print "OK: port directory $k found.\n" + if ($verbose); + } + } + } + foreach $i (@linestocheck) { + $tmp =~ s/$i[?+]?=[^\n]+\n//g; + } + + &checkextra($tmp, '*_DEPENDS'); + + $idx++; + } + + push(@varnames, @linestocheck); + &checkearlier($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($tmp, @varnames); + + # check WRKSRC/NO_WRKSUBDIR + # + # do not use DISTFILES/DISTNAME to control over WRKSRC. + # DISTNAME is for controlling distribution filename. + # example: + # DISTNAME= package + # PKGNAME= package-1.0 + # 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: WRKSRC is set to meaningless value ". + "\"$1\".". + ($nowrksubdir eq '' + ? " use \"NO_WRKSUBDIR=yes\" instead." + : "")); + } + if ($bogusdistfiles) { + if ($distname ne '' && $wrksrc eq '') { + &perror("WARN: do not use DISTFILES and DISTNAME ". + "to control WRKSRC. how about ". + "\"WRKSRC=\${WRKDIR}/$distname\"?"); + } else { + &perror("WARN: 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: definition of WRKSRC not necessery. ". + "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: \"$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: \"$1\" was obsoleted. remove this."); + } + + # check MAN[1-9LN] + print "OK: checking MAN[0-9LN].\n" if ($verbose); + foreach $i (keys %plistmanall) { + print "OK: PLIST MAN$i=$plistmanall{$i}\n" if ($verbose); + } + foreach $i (split(//, $manchapters)) { + if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { + print "OK: Makefile MAN$i=$1\n" if ($verbose); + } + } + foreach $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 $j (@mman) { + print "OK: checking $j (Makefile)\n" + if ($verbose); + if ($automan && grep($_ eq $j, @pman)) { + &perror("FATAL: duplicated manpage ". + "entry $j: content of ". + "MAN$i will be automatically ". + "added to PLIST."); + } elsif (!$automan && !grep($_ eq $j, @pman)) { + &perror("WARN: manpage $j in $file ". + "MAN$i but not in PLIST."); + } + } + foreach $j (@pman) { + print "OK: checking $j (PLIST)\n" if ($verbose); + if (!grep($_ eq $j, @mman)) { + &perror("WARN: manpage $j in PLIST ". + "but not in $file MAN$i."); + } + } + } else { + if ($plistmanall{$i}) { + if ($manstrict) { + &perror("FATAL: manpage for chapter ". + "$i must be listed in ". + "$file MAN\U$i\E. "); + } else { + &perror("WARN: manpage for chapter ". + "$i should be listed in ". + "MAN\U$i\E, ". + "even if compression is ". + "not necessery."); + } + } + if ($mancompress && $plistman{$i}) { + &perror("WARN: MAN\U$i\E will help you ". + "compressing manual page in chapter ". + "\"$i\"."); + } elsif (!$mancompress && $plistmangz{$i}) { + &perror("WARN: 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: how about using MANLANG for ". + "designating manual language, such as \"$i\"?"); + } + + # check USE_X11 and USE_IMAKE + if ($tmp =~ /\nUSE_IMAKE[?+]?=/ + && $tmp =~ /\n(USE_X11)[?+]?=/) { + &perror("WARN: 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: 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: meaning of USE_X11 is 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: direct redefinition of make target \"$1\" ". + "discouraged. redefine \"do-$1\" instead."); + } + + 1; +} + +sub perror { + local(@msg) = @_; + if ($msg[0] =~ /^FATAL/) { + $err++; + } else { + $warn++; + } + print join("\n", @msg) . "\n"; +} + +sub checkextra { + local($str, $section) = @_; + + $str = "\n" . $str if ($str !~ /^\n/); + $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: extra item placed in the ". + "$section section, ". + "for example, \"$1\"."); + } else { + &perror("WARN: extra item placed in the ". + "$section section."); + } +} + +sub checkorder { + local($section, $str, @order) = @_; + local(@items, $i, $j, $k, $invalidorder); + + print "OK: checking the order of $section section.\n" if ($verbose); + + @items = (); + foreach $i (split("\n", $tmp)) { + $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: $i appears out-of-order."); + $invalidorder++; + } else { + print "OK: seen $i, in order.\n" if ($verbose); + } + $j = $k; + } else { + &perror("FATAL: extra item \"$i\" placed in the ". + "$section section."); + } + } + if ($invalidorder) { + &perror("FATAL: order must be " . join('/', @order) . '.'); + } else { + print "OK: $section section is ordered properly.\n" + if ($verbose); + } +} + +sub checkearlier { + local($str, @varnames) = @_; + local($i); + + print "OK: checking items that has to appear earlier.\n" if ($verbose); + foreach $i (@varnames) { + if ($str =~ /\n$i[?+]?=/) { + &perror("WARN: \"$i\" has to appear earlier in $file."); + } + } +} + +sub abspathname { + local($str, $file) = @_; + local($s, $i, %cmdnames); + local($pre); + + # 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 $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: possible use of absolute pathname ". + "\"$i\", in $file."); + } + } + + print "OK: checking direct use of pathnames, phase 1.\n" if ($verbose); +%cmdnames = split(/\n|\t+/, <<EOF); +/usr/opt \${PORTSDIR} instead +$portsdir \${PORTSDIR} instead +$localbase \${PREFIX} or \${LOCALBASE}, as appropriate +/usr/X11 \${PREFIX} or \${X11BASE}, as appropriate +EOF + foreach $i (keys %cmdnames) { + if ($str =~ /$i/) { + &perror("WARN: possible direct use of \"$&\" ". + "found in $file. if so, use $cmdnames{$i}."); + } + } + + print "OK: checking direct use of pathnames, phase 2.\n" if ($verbose); +%cmdnames = split(/\n|\t+/, <<EOF); +distfiles \${DISTDIR} instead +pkg \${PKGDIR} instead +files \${FILESDIR} instead +scripts \${SCRIPTDIR} instead +patches \${PATCHDIR} instead +work \${WRKDIR} instead +EOF + foreach $i (keys %cmdnames) { + if ($str =~ /(\.\/|\$[\{\(]\.CURDIR[\}\)]\/|[ \t])(\b$i)\//) { + &perror("WARN: possible direct use of \"$i\" ". + "found in $file. if so, use $cmdnames{$i}."); + } + } +} + +sub is_predefined { + local($url) = @_; + local($site); + local($subdir); + if ($site = (grep($url =~ $_, keys %predefined))[0]) { + $url =~ /$site/; + $subdir = $'; + $subdir =~ s/\/$//; + &perror("WARN: how about using ". + "\${MASTER_SITE_$predefined{$site}} with ". + "\"MASTER_SITE_SUBDIR=$subdir\", instead of \"$url\?"); + return &TRUE; + } + undef; +} + +sub TRUE {1;} diff --git a/ports-mgmt/portlint/Makefile b/ports-mgmt/portlint/Makefile index 9392ace79cae..22227e56de21 100644 --- a/ports-mgmt/portlint/Makefile +++ b/ports-mgmt/portlint/Makefile @@ -1,28 +1,29 @@ # New ports collection makefile for: portlint -# Version required: 1.61 +# Version required: (self contained) # Date created: 13 Jun 1997 # Whom: Jun-ichiro itojun Itoh <itojun@itojun.org> # -# $Id: Makefile,v 1.19 1997/11/25 11:43:15 itojun Exp $ +# $Id: Makefile,v 1.20 1998/06/27 05:18:50 asami Exp $ # -DISTNAME= portlint-1.61 +DISTNAME= portlint-2.0 CATEGORIES= devel -MASTER_SITES= ftp://ftp.foretune.co.jp/pub/tools/portlint/ -EXTRACT_SUFX= .shar MAINTAINER= itojun@itojun.org -EXTRACT_CMD= ${CAT} -EXTRACT_BEFORE_ARGS= # yes, it is blank -EXTRACT_AFTER_ARGS= |sh -NO_WRKSUBDIR= yes +NO_WRKDIR= yes +NO_EXTRACT= yes NO_BUILD= yes +SRCDIR= ${.CURDIR}/src + MAN1= portlint.1 +do-fetch: + @${DO_NADA} + do-install: - ${INSTALL_SCRIPT} ${WRKDIR}/portlint.pl ${PREFIX}/bin/portlint - ${INSTALL_MAN} ${WRKDIR}/portlint.1 ${MAN1PREFIX}/man/man1 + ${INSTALL_SCRIPT} ${WRKSRC}/portlint.pl ${PREFIX}/bin/portlint + ${INSTALL_MAN} ${WRKSRC}/portlint.1 ${MAN1PREFIX}/man/man1 .include <bsd.port.mk> diff --git a/ports-mgmt/portlint/src/portlint.1 b/ports-mgmt/portlint/src/portlint.1 new file mode 100644 index 000000000000..72d13a183457 --- /dev/null +++ b/ports-mgmt/portlint/src/portlint.1 @@ -0,0 +1,120 @@ +.\" $Id: portlint.1,v 1.8 1997/11/25 14:53:14 itojun Exp $ +.\" +.\" Copyright (c) 1997 by Jun-ichiro Itoh <itojun@itojun.org>. +.\" All Rights Reserved. Absolutely no warranty. +.\" +.Dd July 11, 1997 +.Dt PORTLINT 1 +.Sh NAME +.Nm portlint +.Nd a verifier for port directory +.Sh SYNOPSIS +.Nm portlint +.Op Fl abchvN +.Op Fl B Ar n +.Op Ar dir +.Sh DESCRIPTION +.Nm +tries to verify the content of a port directory. +The purpose of +.Nm +can be separated into two parts: +.Pq 1 +to let the submitters easily polish her/his own port directory, and +.Pq 2 +to decrease the labor of the committers. +.Pp +.Nm +uses very simple regular-expression matching for verifying +files that make up a port directory. +Note that it does NOT implement complete parser for those files. +Because of this the user may see some extra warnings, +especially when checking complex +.Pa Makefile Ns No s . +.Pp +.Sy Options +.Bl -tag -width Fl +.It Fl a +Perform additional checks for extra files, such as +.Pa scripts/* +and +.Pa pkg/* . +.It Fl b +Warn the use of +.Pa $(VARIABLE) . +Some of the committers prefer +.Pa ${VARIABLE} +instead of +.Pa $(VARIABLE) , +even though they are semantically same. +.It Fl c +Committer flag. +It will add several checks useful only for committers. +If you are a committer and performing check just before commiting a port, +use this option. +.It Fl h +Show the summary of command line options, then exit. +.It Fl v +Be verbose. +Show the progress report for items that are being checked. +.It Fl N +New port flag. +Adds several checks specific to newly submitted port. +If you are willing to submit the directory to be checked as a new port, +use this option. +.It Fl B Ar n +Set the number of contiguous blank lines allowed in +.Pa Makefile +to +.Ar n . +(by default, +.Ar n +is 1) +.It dir +The port directory to be checked. +If omitted, check will be performed over the current directory. +.El +.Sh DIAGNOSTICS +Messages will be sent to standard output, not standard error output. +.Bl -tag -width WARN: foobaa +.It FATAL: ... +This type of error messages suggest that there is some fatal error +in the port directory. +For example, if some files need a rewrite, or if +some inevitable files are missing, this message will show up. +This kind of errors should be avoided BEFORE submitting +a port via send-pr to the comitters. +.\"If a submitter submit it without update, committers will need to rewrite +.\"on behalf of the submitters, which may result in delay of +.\"the development of operating system itself. +.It WARN: ... +This type of error messages suggest that some files may (or may not) +need some fix. +Basically, warnings are produced when +.Nm +is not completely sure about the result. +For example, complex +.Pa Makefile Ns No s +may need some statements that can match the regular expression +.Nm +uses for sanity checks. +In those cases, the user should evaluate the result manually, +and obey/ignore the result. +.It OK: ... +This type of messages are used in verbose mode +.Pq Fl v . +.El +.Sh FILES +.Bl -tag -width /usr/share/mk/bsd.port.mk -compact +.It Pa /usr/share/mk/bsd.port.mk +master Makefile for ports +.It Pa /usr/ports/* +port collection +.Sh AUTHORS +Jun-ichiro Itoh <itojun@itojun.org> +and +Yoshishige Arai <ryo2@on.rim.or.jp>. +Many people has contributed patches and comments/suggestions. +.Sh BUGS +.Nm +is not a magic wand, as described above. diff --git a/ports-mgmt/portlint/src/portlint.pl b/ports-mgmt/portlint/src/portlint.pl new file mode 100644 index 000000000000..6ab44b4bf681 --- /dev/null +++ b/ports-mgmt/portlint/src/portlint.pl @@ -0,0 +1,1342 @@ +#! /usr/bin/perl +# +# portlint - lint for port directory +# implemented by: +# Jun-ichiro itojun Itoh <itojun@itojun.org> +# Yoshishige Arai <ryo2@on.rim.or.jp> +# visit ftp://ftp.foretune.co.jp/pub/tools/portlint/ for latest version. +# +# Copyright(c) 1997 by Jun-ichiro Itoh <itojun@itojun.org>. +# All rights reserved. +# Freely redistributable. Absolutely no warranty. +# +# Pleae note that this perl code MUST 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... +# +# $Id: portlint.pl,v 1.65 1998/03/07 04:17:55 itojun Exp $ +# + +$err = $warn = 0; +$extrafile = $parenwarn = $committer = $verbose = $newport = 0; +$contblank = 1; +$portdir = '.'; + +# default setting - for FreeBSD +$portsdir = '/usr/ports'; +$rcsidstr = '(Id|FreeBSD)'; +$multiplist = 0; +$ldconfigwithtrue = 0; +$rcsidinplist = 0; +$mancompress = 1; +$manstrict = 0; +$newxdef = 1; +$automan = 1; +$manchapters = '123456789ln'; +$localbase = '/usr/local'; + +#select(STDERR); +while (@ARGV > 0) { + $_ = shift; + /^-h/ && do { + ($prog) = ($0 =~ /([^\/]+)$/); + print STDERR <<EOF; +usage: $prog [-abcvN] [-B#] [port_directory] + -a additional check for scripts/* and pkg/* + -b warn \$(VARIABLE) + -c committer mode + -v verbose mode + -N writing a new port + -B# allow # contiguous blank lines (default: $contblank line) +EOF + exit 0; + }; + /^-a/ && do {$extrafile = 1; next;}; + /^-b/ && do {$parenwarn = 1; next;}; + /^-c/ && do {$committer = 1; next;}; + /^-v/ && do {$verbose = 1; next;}; + /^-N/ && do {$newport = 1; next;}; + /^-B(\d+)$/ && do { $contblank = $1; next; }; + @ARGV > 0 && /^-B$/ && do { + $contblank = shift; + if ($contblank !~ /^\d+$/) { + print STDERR "FATAL: -B must come with number.\n"; + exit 1; + } + next; + }; + $portdir = $_; +} + +# OS dependent configs +# os portsdir rcsid mplist ldcfg plist-rcsid mancompresss strict localbase newxdef automan +@osdep = split(/\n/, <<EOF); +FreeBSD /usr/ports (Id|FreeBSD) 0 0 0 1 0 /usr/local 1 1 +NetBSD /usr/pkgsrc NetBSD 1 1 1 0 1 /usr/pkg 0 0 +EOF +$osname = `uname -s`; +$osname =~ s/\n$//; +foreach $i (@osdep) { + if ($i =~ /^$osname\t(.*)/) { + print "OK: found OS config for $osname.\n" if ($verbose); + ($portsdir, $rcsidstr, $multiplist, $ldconfigwithtrue, + $rcsidinplist, $mancompress, $manstrict, $localbase, + $newxdef, $automan) + = split(/\t+/, $1); + last; + } +} +if ($verbose) { + print "OK: config: portsdir: \"$portsdir\" ". + "rcsidstr: \"$rcsidstr\" ". + "multiplist: $multiplist ". + "ldconfigwithtrue: $ldconfigwithtrue ". + "rcsidinplist: $rcsidinplist ". + "mancompress: $mancompress ". + "manstrict: $manstrict ". + "localbase: $localbase ". + "\n"; +} + +# +# just for safety. +# +if (! -d $portdir) { + print STDERR "FATAL: invalid directory $portdir specified.\n"; + exit 1; +} + +# +# variables for global checks. +# +$sharedocused = 0; +%plistmanall = (); +%plistmangz = (); +%plistman = (); +%manlangs = (); + +%predefined = (); +foreach $i (split("\n", <<EOF)) { +XCONTRIB ftp://ftp.x.org/contrib/ +XCONTRIB ftp://crl.dec.com/pub/X11/contrib/ +GNU ftp://prep.ai.mit.edu/pub/gnu/ +GNU ftp://wuarchive.wustl.edu/systems/gnu/ +PERL_CPAN ftp://ftp.digital.com/pub/plan/perl/CPAN/modules/by-module/ +PERL_CPAN ftp://ftp.cdrom.com/pub/perl/CPAN/modules/by-module/ +TEX_CTAN ftp://ftp.cdrom.com/pub/tex/ctan/ +TEX_CTAN ftp://wuarchive.wustl.edu/packages/TeX/ +TEX_CTAN ftp://ftp.funet.fi/pub/TeX/CTAN/ +TEX_CTAN ftp://ftp.tex.ac.uk/public/ctan/tex-archive/ +TEX_CTAN ftp://ftp.dante.de/tex-archive/ +SUNSITE ftp://sunsite.unc.edu/pub/Linux/ +SUNSITE ftp://ftp.infomagic.com/pub/mirrors/linux/sunsite/ +SUNSITE ftp://ftp.funet.fi/pub/mirrors/sunsite.unc.edu/pub/Linux/ +EOF + ($j, $k) = split(/\t+/, $i); + $predefined{$k} = $j; +} + +# +# check for files. +# +@checker = ('pkg/COMMENT', 'pkg/DESCR', 'Makefile', 'files/md5'); +%checker = ('pkg/COMMENT', 'checkdescr', 'pkg/DESCR', 'checkdescr', + 'Makefile', 'checkmakefile', 'files/md5', 'TRUE'); +if ($extrafile) { + foreach $i ((<$portdir/scripts/*>, <$portdir/pkg/*>)) { + next if (! -T $i); + $i =~ s/^$portdir\///; + 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/^$portdir\///; + next if (defined $checker{$i}); + push(@checker, $i); + $checker{$i} = 'checkpatch'; +} +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 ($committer) { + if (scalar(@_ = <$portdir/work/*>) || -d "$portdir/work") { + &perror("WARN: be sure to cleanup $portdir/work ". + "before committing the port."); + } + if (scalar(@_ = <$portdir/*/*~>) || scalar(@_ = <$portdir/*~>)) { + &perror("WARN: for safety, be sure to cleanup ". + "emacs backup files before committing the port."); + } + 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 port."); + } +} +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 (<IN>) { + $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: $i includes lines that exceed $maxchars{$file} ". + "charactors."); + } + if ($tmp =~ /[\033\200-\377]/) { + &perror("WARN: pkg/DESCR includes iso-8859-1, or ". + "other local characters. $file should be". + "plain ascii file."); + } + close(IN); +} + +# +# 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 (<IN>) { + 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 + } 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)?$#) { + 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: 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 (<IN>) { + $whole .= $_; + } + &abspathname($whole, $file); + close(IN); +} + +sub checklastline { + local($file) = @_; + local($whole); + + open(IN, "< $portdir/$file") || return 0; + $whole = ''; + while (<IN>) { + $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 unnecessery blank lines ". + "at the last part."); + } + + close(IN); +} + +sub checkpatch { + local($file) = @_; + local($whole); + + if (-z "$portdir/$file") { + &perror("FATAL: $file has no content. should be removed ". + "from repository."); + return; + } + + open(IN, "< $portdir/$file") || return 0; + $whole = ''; + while (<IN>) { + $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 { + 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 (<IN>) { + 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."); + } + + # + # whole file: direct use of command names + # + print "OK: checking direct use of command names.\n" if ($verbose); + foreach $i (split(/\s+/, <<EOF)) { +awk basename cat cp echo false gmake grep gzcat +ldconfig md5 mkdir mv patch rm rmdir sed setenv touch tr xmkmf +EOF + $cmdnames{$i} = "\$\{\U$i\E\}"; + } + $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 $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: ${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); + + # + # break the makefile into sections. + # + @sections = split(/\n\n+/, $rawwhole); + for ($i = 0; $i < scalar(@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); + @linestocheck = split("\n", <<EOF); +Whom +Version [rR]equired +Date [cC]reated +EOF + if ($osname eq 'NetBSD') { + unshift(@linestocheck, '(New )?[pP](ackage|ort)s [cC]ollection [mM]akefile [fF]or'); + } else { + unshift(@linestocheck, '(New )?[pP]orts [cC]ollection [mM]akefile [fF]or'); + } + $tmp = $sections[$idx++]; + $tmp = "\n" . $tmp; # to make the begin-of-line check easier + + if ($tmp =~ /\n[^#]/) { + &perror("FATAL: non-comment line in comment section of $file."); + } + foreach $i (@linestocheck) { + $j = $i; + $j =~ s/\(.*\)\?//g; + $j =~ s/\[(.)[^\]]*\]/$1/g; + if ($tmp !~ /# $i:[ \t]+\S+/) { + &perror("FATAL: no \"$j\" line in ". + "comment section of $file."); + } else { + print "OK: \"$j\" seen in $file.\n" if ($verbose); + } + } + if ($tmp !~ /#\n#(\s+)\$$rcsidstr([^\$]*)\$\n/) { + &perror("FATAL: no \$$rcsidstr\$ line in $file comment ". + "section."); + } else { + print "OK: \$$rcsidstr\$ seen in $file.\n" if ($verbose); + if ($1 ne ' ') { + &perror("WARN: please use single whitespace ". + "right before \$$rcsidstr\$ tag."); + } + if ($2 ne '') { + if ($verbose || $newport) { # XXX + &perror("WARN: ". + ($newport ? 'for new port, ' + : 'is it a new port? if so, '). + "make \$$rcsidstr\$ tag in comment ". + "section empty, to make CVS happy."); + } + } + } + + # + # for the rest of the checks, comment lines are not important. + # + for ($i = 0; $i < scalar(@sections); $i++) { + $sections[$i] =~ s/\n#[^\n]*//g; + $sections[$i] =~ s/\n\n+/\n/g; + $sections[$i] =~ s/\\\n/ /g; + } + + # + # + # section 2: DISTNAME/PKGNAME/... + # + print "OK: checking first section of $file. (DISTNAME/...)\n" + if ($verbose); + $tmp = $sections[$idx++]; + + # check the order of items. + &checkorder('DISTNAME', $tmp, split(/\s+/, <<EOF)); +DISTNAME PKGNAME CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR +EXTRACT_SUFX DISTFILES +EOF + + # check the items that has to be there. + $tmp = "\n" . $tmp; + foreach $i ('DISTNAME', 'CATEGORIES') { + print "OK: checking $i.\n" if ($verbose); + if ($tmp !~ /\n$i=/) { + &perror("FATAL: $i has to be there."); + } + if ($tmp =~ /\n$i(\?=)/) { + &perror("FATAL: $i has to be set by \"=\", ". + "not by \"$1\"."); + } + } + + # check x11 in CATEGORIES + if ($newxdef + && $tmp =~ /\nCATEGORIES[+?]?=[ \t]*([^\n]*)\n/ && $1 =~ /x11/) { + print "OK: checking x11 in CATEGORIES.\n" if ($verbose); + @i = split(/\s+/, $1); + if ($i[0] eq 'x11') { + ; # okay + } elsif ($i[0] =~ /(chinese|japanese|korean|german|russian)/) { + ; # okay + } else { + &perror("WARN: only specific kind of apps should ". + "specify \"x11\" in CATEGORIES. ". + "do you mean just USE_XLIB? ". + "then remove \"x11\" from CATEGORIES."); + } + } + + # check the URL + if ($tmp =~ /\nMASTER_SITES[+?]?=[ \t]*([^\n]*)\n/ + && $1 !~ /^[ \t]*$/) { + print "OK: seen MASTER_SITES, sanity checking URLs.\n" + if ($verbose); + @sites = split(/\s+/, $1); + foreach $i (@sites) { + if ($i =~ m#^\w+://#) { + if ($i !~ m#/$#) { + &perror("FATAL: URL \"$i\" should ". + "end with \"/\"."); + } + if ($i =~ m#://[^/]*:/#) { + &perror("FATAL: URL \"$i\" contains ". + "extra \":\"."); + } + unless (&is_predefined($i)) { + print "OK: URL \"$i\" ok.\n" + if ($verbose); + } + } else { + print "OK: non-URL \"$i\" ok.\n" + if ($verbose); + } + } + } else { + &perror("WARN: no MASTER_SITES found. is it ok?"); + } + + # check DISTFILES and related items. + $distfiles = $1 if ($tmp =~ /\nDISTFILES[+?]?=[ \t]*([^\n]+)\n/); + $pkgname = $1 if ($tmp =~ /\nPKGNAME[+?]?=[ \t]*([^\n]+)\n/); + $distname = $1 if ($tmp =~ /\nDISTNAME[+?]?=[ \t]*([^\n]+)\n/); + $extractsufx = $1 if ($tmp =~ /\nEXTRACT_SUFX[+?]?=[ \t]*([^\n]+)\n/); + + # check bogus EXTRACT_SUFX. + if ($extractsufx ne '') { + print "OK: seen EXTRACT_SUFX, checking value.\n" if ($verbose); + if ($distfiles ne '') { + &perror("WARN: no need to define EXTRACT_SUFX if ". + "DISTFILES is defined."); + } + if ($extractsufx eq '.tar.gz') { + &perror("WARN: EXTRACT_SUFX is \".tar.gz.\" ". + "by default. you don't need to specify it."); + } + } else { + print "OK: no EXTRACT_SUFX seen, using default value.\n" + if ($verbose); + $extractsufx = '.tar.gz'; + } + + print "OK: sanity checking PKGNAME.\n" if ($verbose); + if ($pkgname ne '' && $pkgname eq $distname) { + &perror("WARN: PKGNAME is \${DISTNAME} by default, ". + "you don't need to define PKGNAME."); + } + $i = ($pkgname eq '') ? $distname : $pkgname; + if ($i =~ /-([^-]+)$/) { + $j = $`; + $k = $1; + if ($j =~ /[0-9]$/) { + &perror("WARN: is \"$j\" sane as package name ". + "WITHOUT version number? ". + "if not, avoid \"-\" in version number ". + "part of ". + (($pkgname eq '') ? "DISTNAME." : "PKGNAME.")); + } + if ($k =~ /^pl[0-9]*$/ + || $k =~ /^[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9]*)*$/) { + print "OK: trailing part of PKGNAME\"-$k\" ". + "looks fine.\n" if ($verbose); + } else { + &perror("FATAL: version number part of PKGNAME". + (($pkgname eq '') + ? ', which is derived from DISTNAME, ' + : ' '). + "looks illegal. should modify \"-$k\"."); + } + } else { + &perror("FATAL: PKGNAME". + (($pkgname eq '') + ? ', which is derived from DISTNAME, ' + : ' '). + "must come with version number, like \"foobaa-1.0\"."); + if ($i =~ /_pl[0-9]*$/ + || $i =~ /_[0-9]*[A-Za-z]?[0-9]*(\.[0-9]*[A-Za-z]?[0-9]*)*$/) { + &perror("FATAL: you seem to using underline ". + "before version number in PKGNAME. ". + "it has to be hyphen."); + } + } + + # 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+$/) { + $bogusdistfiles++; + print "OK: seen DISTFILES with single item, checking value.\n" + if ($verbose); + &perror("WARN: use of DISTFILES with single file ". + "discouraged. distribution filename should be set by ". + "DISTNAME and EXTRACT_SUFX."); + if ($distfiles eq $distname . $extractsufx) { + &perror("WARN: definition of DISTFILES not necessery. ". + "DISTFILES is \${DISTNAME}/\${EXTRACT_SUFX} ". + "by default."); + } + + # make an advice only in certain cases. + if ($pkgname ne '' && $distfiles =~ /^$pkgname([-\.].+)$/) { + &perror("WARN: how about \"DISTNAME=$pkgname\"". + (($1 eq '.tar.gz') + ? "" + : " and \"EXTRACT_SUFX=$1\""). + ", instead of DISTFILES?"); + } + } + + # additional checks for committer. + $i = ($pkgname eq '') ? $distname : $pkgname; + if ($committer && $i =~ /^(de|ja|ko|ru|vi|zh)-/) { + &perror("WARN: be sure to include country code \"$1-\" ". + "in the module alias name."); + } + if ($committer && -f "$portdir/$i.tgz") { + &perror("WARN: be sure to remove $portdir/$i.tgz ". + "before committing the port."); + } + + push(@varnames, split(/\s+/, <<EOF)); +DISTNAME PKGNAME CATEGORIES MASTER_SITES MASTER_SITE_SUBDIR +EXTRACT_SUFX DISTFILES +EOF + + # + # section 3: PATCH_SITES/PATCHFILES(optional) + # + print "OK: checking second section of $file, (PATCH*: optinal).\n" + if ($verbose); + $tmp = $sections[$idx]; + + if ($tmp =~ /(PATCH_SITES|PATCH_SITE_SUBDIR|PATCHFILES|PATCH_DIST_STRIP)/) { + &checkearlier($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'); + + $idx++; + } + + push(@varnames, split(/\s+/, <<EOF)); +PATCH_SITES PATCHFILES PATCH_DIST_STRIP +EOF + + # + # section 4: MAINTAINER + # + print "OK: checking third section of $file (MAINTAINER).\n" + if ($verbose); + $tmp = $sections[$idx++]; + + &checkearlier($tmp, @varnames); + $tmp = "\n" . $tmp; + if ($tmp =~ /\nMAINTAINER=[^\n]+/) { + $tmp =~ s/\nMAINTAINER=[^\n]+//; + } else { + &perror("FATAL: no MAINTAINER listed in $file."); + } + $tmp =~ s/\n\n+/\n/g; + + &checkextra($tmp, 'MAINTAINER'); + + push(@varnames, 'MAINTAINER'); + + # + # 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 = split(/\s+/, <<EOF); +LIB_DEPENDS BUILD_DEPENDS RUN_DEPENDS FETCH_DEPENDS DEPENDS DEPENDS_TARGET +EOF + if ($tmp =~ /(LIB_|BUILD_|RUN_|FETCH_)?DEPENDS/) { + &checkearlier($tmp, @varnames); + + if (!defined $ENV{'PORTSDIR'}) { + $ENV{'PORTSDIR'} = $portsdir; + } + foreach $i (grep(/^[A-Z_]*DEPENDS[?+]?=/, split(/\n/, $tmp))) { + $i =~ s/^([A-Z_]*DEPENDS)[?+]?=[ \t]*//; + $j = $1; + print "OK: checking ports listed in $j.\n" + if ($verbose); + foreach $k (split(/\s+/, $i)) { + @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: wrong dependency value ". + "for $j. $j requires ". + ($j eq 'DEPENDS' + ? "1 or 2 " + : "2 or 3 "). + "colon-separated tuples."); + next; + } + %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: dependency to perl5 ". + "listed in $j. consider using ". + "USE_PERL5."); + } + + # check USE_GMAKE + if ($m{'dep'} =~ /^(gmake|\${GMAKE})$/) { + &perror("WARN: dependency to $1 ". + "listed in $j. consider using ". + "USE_GMAKE."); + } + + # check USE_QT + if ($m{'dep'} =~ /^(qt\d)+$/) { + &perror("WARN: dependency to $1 ". + "listed in $j. consider using ". + "USE_QT."); + } + + # check backslash in LIB_DEPENDS + if ($osname eq 'NetBSD' && $j eq 'LIB_DEPENDS' + && $m{'dep'} =~ /\\\\./) { + &perror("WARN: use of backslashes in ". + "$j is deprecated."); + } + + # check port dir existence + $k = $m{'dir'}; + $k =~ s/\${PORTSDIR}/$ENV{'PORTSDIR'}/; + if (! -d $k) { + &perror("WARN: no port directory $k ". + "found, even though it is ". + "listed in $j."); + } else { + print "OK: port directory $k found.\n" + if ($verbose); + } + } + } + foreach $i (@linestocheck) { + $tmp =~ s/$i[?+]?=[^\n]+\n//g; + } + + &checkextra($tmp, '*_DEPENDS'); + + $idx++; + } + + push(@varnames, @linestocheck); + &checkearlier($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($tmp, @varnames); + + # check WRKSRC/NO_WRKSUBDIR + # + # do not use DISTFILES/DISTNAME to control over WRKSRC. + # DISTNAME is for controlling distribution filename. + # example: + # DISTNAME= package + # PKGNAME= package-1.0 + # 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: WRKSRC is set to meaningless value ". + "\"$1\".". + ($nowrksubdir eq '' + ? " use \"NO_WRKSUBDIR=yes\" instead." + : "")); + } + if ($bogusdistfiles) { + if ($distname ne '' && $wrksrc eq '') { + &perror("WARN: do not use DISTFILES and DISTNAME ". + "to control WRKSRC. how about ". + "\"WRKSRC=\${WRKDIR}/$distname\"?"); + } else { + &perror("WARN: 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: definition of WRKSRC not necessery. ". + "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: \"$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: \"$1\" was obsoleted. remove this."); + } + + # check MAN[1-9LN] + print "OK: checking MAN[0-9LN].\n" if ($verbose); + foreach $i (keys %plistmanall) { + print "OK: PLIST MAN$i=$plistmanall{$i}\n" if ($verbose); + } + foreach $i (split(//, $manchapters)) { + if ($tmp =~ /MAN\U$i\E=\s*([^\n]*)\n/) { + print "OK: Makefile MAN$i=$1\n" if ($verbose); + } + } + foreach $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 $j (@mman) { + print "OK: checking $j (Makefile)\n" + if ($verbose); + if ($automan && grep($_ eq $j, @pman)) { + &perror("FATAL: duplicated manpage ". + "entry $j: content of ". + "MAN$i will be automatically ". + "added to PLIST."); + } elsif (!$automan && !grep($_ eq $j, @pman)) { + &perror("WARN: manpage $j in $file ". + "MAN$i but not in PLIST."); + } + } + foreach $j (@pman) { + print "OK: checking $j (PLIST)\n" if ($verbose); + if (!grep($_ eq $j, @mman)) { + &perror("WARN: manpage $j in PLIST ". + "but not in $file MAN$i."); + } + } + } else { + if ($plistmanall{$i}) { + if ($manstrict) { + &perror("FATAL: manpage for chapter ". + "$i must be listed in ". + "$file MAN\U$i\E. "); + } else { + &perror("WARN: manpage for chapter ". + "$i should be listed in ". + "MAN\U$i\E, ". + "even if compression is ". + "not necessery."); + } + } + if ($mancompress && $plistman{$i}) { + &perror("WARN: MAN\U$i\E will help you ". + "compressing manual page in chapter ". + "\"$i\"."); + } elsif (!$mancompress && $plistmangz{$i}) { + &perror("WARN: 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: how about using MANLANG for ". + "designating manual language, such as \"$i\"?"); + } + + # check USE_X11 and USE_IMAKE + if ($tmp =~ /\nUSE_IMAKE[?+]?=/ + && $tmp =~ /\n(USE_X11)[?+]?=/) { + &perror("WARN: 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: 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: meaning of USE_X11 is 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: direct redefinition of make target \"$1\" ". + "discouraged. redefine \"do-$1\" instead."); + } + + 1; +} + +sub perror { + local(@msg) = @_; + if ($msg[0] =~ /^FATAL/) { + $err++; + } else { + $warn++; + } + print join("\n", @msg) . "\n"; +} + +sub checkextra { + local($str, $section) = @_; + + $str = "\n" . $str if ($str !~ /^\n/); + $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: extra item placed in the ". + "$section section, ". + "for example, \"$1\"."); + } else { + &perror("WARN: extra item placed in the ". + "$section section."); + } +} + +sub checkorder { + local($section, $str, @order) = @_; + local(@items, $i, $j, $k, $invalidorder); + + print "OK: checking the order of $section section.\n" if ($verbose); + + @items = (); + foreach $i (split("\n", $tmp)) { + $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: $i appears out-of-order."); + $invalidorder++; + } else { + print "OK: seen $i, in order.\n" if ($verbose); + } + $j = $k; + } else { + &perror("FATAL: extra item \"$i\" placed in the ". + "$section section."); + } + } + if ($invalidorder) { + &perror("FATAL: order must be " . join('/', @order) . '.'); + } else { + print "OK: $section section is ordered properly.\n" + if ($verbose); + } +} + +sub checkearlier { + local($str, @varnames) = @_; + local($i); + + print "OK: checking items that has to appear earlier.\n" if ($verbose); + foreach $i (@varnames) { + if ($str =~ /\n$i[?+]?=/) { + &perror("WARN: \"$i\" has to appear earlier in $file."); + } + } +} + +sub abspathname { + local($str, $file) = @_; + local($s, $i, %cmdnames); + local($pre); + + # 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 $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: possible use of absolute pathname ". + "\"$i\", in $file."); + } + } + + print "OK: checking direct use of pathnames, phase 1.\n" if ($verbose); +%cmdnames = split(/\n|\t+/, <<EOF); +/usr/opt \${PORTSDIR} instead +$portsdir \${PORTSDIR} instead +$localbase \${PREFIX} or \${LOCALBASE}, as appropriate +/usr/X11 \${PREFIX} or \${X11BASE}, as appropriate +EOF + foreach $i (keys %cmdnames) { + if ($str =~ /$i/) { + &perror("WARN: possible direct use of \"$&\" ". + "found in $file. if so, use $cmdnames{$i}."); + } + } + + print "OK: checking direct use of pathnames, phase 2.\n" if ($verbose); +%cmdnames = split(/\n|\t+/, <<EOF); +distfiles \${DISTDIR} instead +pkg \${PKGDIR} instead +files \${FILESDIR} instead +scripts \${SCRIPTDIR} instead +patches \${PATCHDIR} instead +work \${WRKDIR} instead +EOF + foreach $i (keys %cmdnames) { + if ($str =~ /(\.\/|\$[\{\(]\.CURDIR[\}\)]\/|[ \t])(\b$i)\//) { + &perror("WARN: possible direct use of \"$i\" ". + "found in $file. if so, use $cmdnames{$i}."); + } + } +} + +sub is_predefined { + local($url) = @_; + local($site); + local($subdir); + if ($site = (grep($url =~ $_, keys %predefined))[0]) { + $url =~ /$site/; + $subdir = $'; + $subdir =~ s/\/$//; + &perror("WARN: how about using ". + "\${MASTER_SITE_$predefined{$site}} with ". + "\"MASTER_SITE_SUBDIR=$subdir\", instead of \"$url\?"); + return &TRUE; + } + undef; +} + +sub TRUE {1;} |