diff options
author | rillig <rillig> | 2005-12-06 14:15:02 +0000 |
---|---|---|
committer | rillig <rillig> | 2005-12-06 14:15:02 +0000 |
commit | 4c78db5c9e00d1cd020a5c897dfeb2577bf32220 (patch) | |
tree | d8a38270393fbc38f8d143554a69b9fec154efab /pkgtools | |
parent | e8421e2157e2a1f1af8123d876b99d259b98f471 (diff) | |
download | pkgsrc-4c78db5c9e00d1cd020a5c897dfeb2577bf32220.tar.gz |
Reordered the complete file. Grouped subroutines by name (checkline_*,
checklines_*, checkfile_*). No functional changes.
Diffstat (limited to 'pkgtools')
-rw-r--r-- | pkgtools/pkglint/files/pkglint.pl | 1114 |
1 files changed, 543 insertions, 571 deletions
diff --git a/pkgtools/pkglint/files/pkglint.pl b/pkgtools/pkglint/files/pkglint.pl index 21c4c5465cb..4d76a547d1b 100644 --- a/pkgtools/pkglint/files/pkglint.pl +++ b/pkgtools/pkglint/files/pkglint.pl @@ -11,7 +11,7 @@ # Freely redistributable. Absolutely no warranty. # # From Id: portlint.pl,v 1.64 1998/02/28 02:34:05 itojun Exp -# $NetBSD: pkglint.pl,v 1.421 2005/12/06 13:23:16 rillig Exp $ +# $NetBSD: pkglint.pl,v 1.422 2005/12/06 14:15:02 rillig Exp $ # # This version contains lots of changes necessary for NetBSD packages # done by: @@ -1271,507 +1271,35 @@ sub resolve_relative_path($) { return $relpath; } -# -# Subroutines to check a single line. -# - -sub checkline_length($$) { - my ($line, $maxlength) = @_; - - if (length($line->text) > $maxlength) { - $line->log_warning("Line too long (should be no more than $maxlength characters)."); - $line->explain( - "Back in the old time, terminals with 80x25 characters were common.", - "And this is still the default size of many terminal emulators.", - "Moderately short lines also make reading easier."); - } -} - -sub checkline_valid_characters($$) { - my ($line, $re_validchars) = @_; - my ($rest); - - ($rest = $line->text) =~ s/$re_validchars//g; - if ($rest ne "") { - my @chars = map { $_ = sprintf("0x%02x", ord($_)); } split(//, $rest); - $line->log_warning(sprintf("Line contains invalid characters (%s).", join(", ", @chars))); - } -} - -sub checkline_valid_characters_in_variable($$) { - my ($line, $re_validchars) = @_; - my ($varname, $rest); - - $rest = $line->text; - if ($rest =~ regex_varassign) { - ($varname, undef, $rest) = ($1, $2, $3); - } else { - return; - } - - $rest =~ s/$re_validchars//g; - if ($rest ne "") { - my @chars = map { $_ = sprintf("0x%02x", ord($_)); } split(//, $rest); - $line->log_warning(sprintf("${varname} contains invalid characters (%s).", join(", ", @chars))); - } -} - -sub checkline_trailing_whitespace($) { - my ($line) = @_; - if ($line->text =~ /\s+$/) { - $line->log_note("Trailing white-space."); - } -} - -sub checkline_rcsid_regex($$$) { - my ($line, $prefix_regex, $prefix) = @_; - my ($id) = ($opt_rcsidstring . ($is_wip ? "|Id" : "")); - - if ($line->text !~ qr"^${prefix_regex}\$(${id})(?::[^\$]*|)\$$") { - $line->log_error("\"${prefix}\$${opt_rcsidstring}\$\" expected."); - return false; - } - return true; -} - -sub checkline_rcsid($$) { - my ($line, $prefix) = @_; - checkline_rcsid_regex($line, quotemeta($prefix), $prefix); -} - -sub checkline_relative_path($$) { - my ($line, $path) = @_; - - if (!$is_wip && $path =~ qr"/wip/") { - $line->log_error("A pkgsrc package must not depend on any outside package."); - } - $path = resolve_relative_path($path); - if ($path =~ regex_unresolved) { - $line->log_info("Unresolved path: \"${path}\"."); - } elsif (!-e "${current_dir}/${path}") { - $line->log_error("\"${path}\" does not exist."); - } -} - -# -# Procedures to check an array of lines, part 1. -# - -sub checklines_trailing_empty_lines($) { - my ($lines) = @_; - my ($last, $max); - - $max = $#{$lines} + 1; - for ($last = $max; $last > 1 && $lines->[$last - 1]->text eq ""; ) { - $last--; - } - if ($last != $max) { - $lines->[$last]->log_note("Trailing empty lines."); - } -} - -# -# Procedures to check a file, part 1. -# - -sub checkfile_DESCR($) { - my ($fname) = @_; - my ($maxchars, $maxlines) = (80, 24); - my ($descr); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_DESCR]"); - - checkperms($fname); - if (!($descr = load_file($fname))) { - log_error($fname, NO_LINE_NUMBER, "Cannot be read."); - return; - } - if (@{$descr} == 0) { - log_error($fname, NO_LINE_NUMBER, "Must not be empty."); - return; - } - - foreach my $line (@{$descr}) { - checkline_length($line, $maxchars); - checkline_trailing_whitespace($line); - checkline_valid_characters($line, regex_validchars); - } - checklines_trailing_empty_lines($descr); - - if (@{$descr} > $maxlines) { - my $line = $descr->[$maxlines]; - - $line->log_warning("File too long (should be no more than $maxlines lines)."); - $line->explain( - "A common terminal size is 80x25 characters. The DESCR file should", - "fit on one screen. It is also intended to give a _brief_ summary", - "about the package's contents."); - } -} - -sub checkfile_distinfo($) { - my ($fname) = @_; - my ($lines, %in_distinfo, %sums); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_distinfo]"); - - checkperms($fname); - if (!($lines = load_file($fname))) { - log_error($fname, NO_LINE_NUMBER, "Cannot be read."); - return; - } - - if (@{$lines} == 0) { - log_error($fname, NO_LINE_NUMBER, "Must not be empty."); - return; - } - - checkline_rcsid($lines->[0], ""); - if (1 <= $#{$lines} && $lines->[1]->text ne "") { - $lines->[1]->log_warning("Empty line expected."); - $lines->[1]->explain("This is merely for aesthetical purposes."); - } - - foreach my $line (@{$lines}[2..$#{$lines}]) { - if ($line->text !~ /^(MD5|SHA1|RMD160|Size) \(([^)]+)\) = (.*)(?: bytes)?$/) { - $line->log_error("Unknown line type."); - next; - } - - my ($alg, $file, $sum) = ($1, $2, $3); - - if ($file =~ /^patch-[A-Za-z0-9]+$/) { - if (open(PATCH, "< ${current_dir}/${patchdir}/${file}")) { - my $data = ""; - foreach my $patchline (<PATCH>) { - $data .= $patchline unless $patchline =~ qr"\$NetBSD.*\$"; - } - close(PATCH); - my $chksum = Digest::SHA1::sha1_hex($data); - if ($sum ne $chksum) { - $line->log_error("${alg} checksum of $file differs (expected ${sum}, got ${chksum}). Rerun '".conf_make." makepatchsum'."); - } - } elsif (!$hack_php_patches) { - $line->log_warning("$file does not exist."); - $line->explain( - "All patches that are mentioned in a distinfo file should actually exist.", - "What's the use of a checksum if there is no file to check?"); - } - } else { - $sums{$alg}->{$file} = $line; - } - $in_distinfo{$file} = true; - } - checklines_trailing_empty_lines($lines); - - # Check for distfiles that have SHA1, but not RMD160 checksums - foreach my $sha1_file (sort(keys(%{$sums{"SHA1"}}))) { - if (!exists($sums{"RMD160"}->{$sha1_file})) { - $sums{"SHA1"}->{$sha1_file}->log_error("RMD160 checksum missing for \"${sha1_file}\"."); - } - } - - foreach my $patch (<${current_dir}/$patchdir/patch-*>) { - $patch = basename($patch); - if (!exists($in_distinfo{$patch})) { - log_error($fname, NO_LINE_NUMBER, "$patch is not recorded. Rerun '".conf_make." makepatchsum'."); - } - } -} - -sub checkfile_MESSAGE($) { - my ($fname) = @_; - my ($message); - - my @explanation = ( - "A MESSAGE file should consist of a header line, having 75 \"=\"", - "characters, followed by a line containing only the RCS Id, then an", - "empty line, your text and finally the footer line, which is the", - "same as the header line."); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_MESSAGE]"); - - checkperms($fname); - if (!($message = load_file($fname))) { - log_error($fname, NO_LINE_NUMBER, "Cannot be read."); - return; - } - - if (@{$message} < 3) { - log_warning($fname, NO_LINE_NUMBER, "File too short."); - explain($fname, NO_LINE_NUMBER, @explanation); - return; - } - if ($message->[0]->text ne "=" x 75) { - $message->[0]->log_warning("Expected a line of exactly 75 \"=\" characters."); - explain($fname, NO_LINE_NUMBER, @explanation); - } - checkline_rcsid($message->[1], ""); - foreach my $line (@{$message}) { - checkline_length($line, 80); - checkline_trailing_whitespace($line); - checkline_valid_characters($line, regex_validchars); - } - if ($message->[-1]->text ne "=" x 75) { - $message->[-1]->log_warning("Expected a line of exactly 75 \"=\" characters."); - explain($fname, NO_LINE_NUMBER, @explanation); - } - checklines_trailing_empty_lines($message); -} - -sub checkfile_PLIST($) { - my ($fname) = @_; - my ($plist, $last_file_seen); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_PLIST]"); - - checkperms($fname); - if (!($plist = load_file($fname))) { - log_error($fname, NO_LINE_NUMBER, "Cannot be read."); - return; - } - if (@{$plist} == 0) { - log_error($fname, NO_LINE_NUMBER, "Must not be empty."); - return; - } - checkline_rcsid($plist->[0], "\@comment "); - - line: - foreach my $line (@{$plist}) { - my $text = $line->text; - - checkline_trailing_whitespace($line); - - if ($text =~ /^\@([a-z]+)\s+(.*)/) { - my ($cmd, $arg) = ($1, $2); - - if ($cmd eq "unexec" && $arg =~ qr"^(rmdir|\$\{RMDIR\} \%D/)(.*)") { - my ($rmdir, $rest) = ($1, $2); - if ($rest !~ qr"(?:true|\$\{TRUE\})") { - $line->log_warning("Please use \"\@dirrm\" instead of \"\@unexec rmdir\"."); - } - - } elsif (($cmd eq "exec" || $cmd eq "unexec")) { - if ($arg =~ /(?:install-info|\$\{INSTALL_INFO\})/) { - $line->log_warning("\@exec/unexec install-info is deprecated."); - - } elsif ($arg =~ /ldconfig/ && $arg !~ qr"/usr/bin/true") { - $line->log_error("ldconfig must be used with \"||/usr/bin/true\"."); - } - - } elsif ($cmd eq "comment" || $cmd eq "dirrm") { - # nothing to do - - } else { - $line->log_warning("Unknown PLIST directive \"\@$cmd\"."); - } - - } elsif ($text =~ qr"^[A-Za-z0-9\$]") { - if ($opt_warn_plist_sort && $text =~ qr"^\w" && $text !~ regex_unresolved) { - if (defined($last_file_seen)) { - if ($last_file_seen gt $text) { - $line->log_warning("${text} should be sorted before ${last_file_seen}."); - } - } - $last_file_seen = $text; - } - - if ($text =~ qr"^doc/") { - $line->log_error("Documentation must be installed under share/doc, not doc."); - - } elsif ($text =~ qr"^etc/rc\.d/") { - $line->log_error("RCD_SCRIPTS must not be registered in the PLIST. Please use the RCD_SCRIPTS framework."); - - } elsif ($text =~ qr"^etc/") { - $line->log_error("Configuration files must not be registered in the PLIST. Please use the CONF_FILES framework, which is described in mk/install/bsd.pkginstall.mk."); - - } elsif ($text eq "info/dir") { - $line->log_error("\"info/dir\" must not be listed. Use install-info to add/remove an entry."); - - } elsif ($text =~ qr"^lib/locale/") { - $line->log_error("\"lib/locale\" must not be listed. Use \${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead."); - - } elsif ($text =~ qr"^share/locale/") { - $line->log_warning("Use of \"share/locale\" is deprecated. Use \${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead."); - - } elsif ($text =~ qr"^share/man/") { - $line->log_warning("Man pages should be installed into man/, not share/man/."); - } - - if ($text =~ /\${PKGLOCALEDIR}/ && defined($makevar) && !exists($makevar->{"USE_PKGLOCALEDIR"})) { - $line->log_warning("PLIST contains \${PKGLOCALEDIR}, but USE_PKGLOCALEDIR was not found."); - } - - if ($text =~ qr"/CVS/") { - $line->log_warning("CVS files should not be in the PLIST."); - } +sub expand_variable($$) { + my ($whole, $varname) = @_; + my ($value, $re); - } else { - $line->log_error("Unknown line type."); + $re = qr"\n${varname}([+:?]?)=[ \t]*([^\n#]*)"; + $value = undef; + while ($whole =~ m/$re/g) { + my ($op, $val) = ($1, $2); + if ($op ne "?" || !defined($value)) { + $value = $val; } } - checklines_trailing_empty_lines($plist); -} - -sub checkfile_extra($) { - my ($fname) = @_; - my ($lines); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_extra]"); - - $lines = load_file($fname); - if (!$lines) { - log_error($fname, NO_LINE_NUMBER, "Could not be read."); - return; - } - checklines_trailing_empty_lines($lines); - checkperms($fname); -} - -# -# Procedures to check an array of lines, part 2. -# - -sub checklines_multiple_patches($) { - my ($lines) = @_; - my ($files_in_patch, $patch_state, $line_type, $dellines); - - $files_in_patch = 0; - $patch_state = ""; - $dellines = 0; - foreach my $line (@{$lines}) { - my $text = $line->text; - - if ($text =~ qr"^@@ -\d+,(\d+) \+\d+,\d+ @@") { - $line_type = ""; - $dellines = $1; - - } elsif ($dellines == 0 && index($text, "--- ") == 0 && $text !~ qr"^--- \d+(?:,\d+|) ----$") { - $line_type = "-"; - - } elsif (index($text, "*** ") == 0 && $text !~ qr"^\*\*\* \d+(?:,\d+|) \*\*\*\*$") { - $line->log_warning("Please use unified diffs (diff -u) for patches."); - $line_type = "*"; - - } elsif (index($text, "+++ ") == 0) { - $line_type = "+"; - - } elsif ($dellines > 0 && $text =~ qr"^(?:-|\s)") { - $line_type = ""; - $dellines--; - - } else { - $line_type = ""; - } - - if ($patch_state eq "*") { - if ($line_type eq "-") { - $files_in_patch++; - $patch_state = ""; - } else { - $line->log_error("[internal] Unknown patch format."); - } - - } elsif ($patch_state eq "-") { - if ($line_type eq "+") { - $files_in_patch++; - $patch_state = ""; - } else { - $line->log_error("[internal] Unknown patch format."); - } - - } elsif ($patch_state eq "") { - $patch_state = $line_type; - } + if (!defined($value)) { + return undef; } - if ($files_in_patch > 1) { - log_warning($lines->[0]->file, NO_LINE_NUMBER, "Contains patches for $files_in_patch files, should be only one."); - - } elsif ($files_in_patch == 0) { - log_error($lines->[0]->file, NO_LINE_NUMBER, "Contains no patch."); + $value = resolve_relative_path($value); + if ($value =~ regex_unresolved) { + log_info(NO_FILE, NO_LINE_NUMBER, "[expand_variable] The variable ${varname} could not be resolved completely. Its value is \"${value}\"."); } + return $value; } -# -# Procedures to check a file, part 2. -# - -sub checkfile_patches_patch($) { - my ($fname) = @_; - my ($lines); - - log_info($fname, NO_LINE_NUMBER, "[checkfile_patches_patch]"); - - checkperms($fname); - if (!($lines = load_file($fname))) { - log_error($fname, NO_LINE_NUMBER, "Could not be read."); - return; - } - if (@{$lines} == 0) { - log_error($fname, NO_LINE_NUMBER, "Must not be empty."); - return; - } - checkline_rcsid($lines->[0], ""); - - foreach my $line (@{$lines}[1..$#{$lines}]) { - if ($line->text =~ qr"\$(Author|Date|Header|Id|Locker|Log|Name|RCSfile|Revision|Source|State|$opt_rcsidstring)[:\$]") { - my ($tag) = ($1); - if ($line->text =~ qr"^(\@\@.*?\@\@)") { - $line->log_warning("Patches should not contain RCS tags."); - $line->set_text($1); - } else { - $line->log_warning("Possible RCS tag \"\$${tag}\$\". Please remove it by reducing the number of context lines using pkgdiff or \"diff -U[210]\"."); - } - } - - if ($line->text =~ qr"^\+") { - use constant good_macros => PkgLint::Util::array_to_hash(qw( - __STDC__ - - __GNUC__ __GNUC_MINOR__ - __SUNPRO_C - - __i386 - __mips - __sparc - - __DragonFly__ - __FreeBSD__ - __INTERIX - __linux__ - __NetBSD__ - __OpenBSD__ - __SVR4 - __sun - - __GLIBC__ - )); - use constant bad_macros => { - "__sparc__" => "__sparc", - "__sun__" => "__sun", - "__svr4__" => "__SVR4", - }; - my $rest = $line->text; - my $re_ifdef = qr"defined\((__[\w_]+)\)"; - - while ($rest =~ s/$re_ifdef//) { - my ($macro) = ($1); +sub set_default_value($$) { + my ($varref, $value) = @_; - if (exists(good_macros->{$macro})) { - $line->log_debug("Found good macro \"${macro}\"."); - } elsif (exists(bad_macros->{$macro})) { - $line->log_warning("The macro \"${macro}\" is unportable. Please use \"".bad_macros->{$macro}."\" instead."); - $line->explain("See the pkgsrc guide, section \"CPP defines\" for details."); - } else { - $line->log_info("Found unknown macro \"${macro}\"."); - } - } - } + if (!defined(${$varref}) || ${$varref} =~ regex_unresolved) { + ${$varref} = $value; } - checklines_trailing_empty_lines($lines); - - checklines_multiple_patches($lines); } # @@ -1859,10 +1387,139 @@ sub readmakefile($$$$) { return $contents; } +sub load_package_Makefile($$$) { + my ($subr) = "load_package_Makefile"; + my ($fname, $ref_whole, $ref_lines) = @_; + my ($whole, $lines, $all_lines); + + log_info($fname, NO_LINE_NUMBER, "Checking package Makefile."); + + $whole = readmakefile($fname, $lines = [], $all_lines = [], {}); + if (!$whole) { + log_error($fname, NO_LINE_NUMBER, "Cannot be read."); + return false; + } + + if ($opt_dumpmakefile) { + print("OK: whole Makefile (with all included files) follows:\n"); + foreach my $line (@{$all_lines}) { + printf("%s\n", $line->to_string()); + } + } + + # HACK + if ($whole =~ qr"\nPHPEXT_MK" && $whole !~ qr"\nUSE_PHP_EXT_PATCHES") { + log_info($fname, NO_LINE_NUMBER, "[hack] USE_PHP_EXT_PATCHES"); + $whole =~ s,\nPATCHDIR=.*PHPPKGSRCDIR.*,,; + $hack_php_patches = true; + } + # HACK + if ($whole =~ qr"\nPECL_VERSION") { + log_info($fname, NO_LINE_NUMBER, "[hack] PECL_VERSION"); + $whole =~ s,\nDISTINFO_FILE=.*PHPPKGSRCDIR.*,,; + } + + $pkgdir = expand_variable($whole, "PKGDIR"); + set_default_value(\$pkgdir, "."); + $distinfo_file = expand_variable($whole, "DISTINFO_FILE"); + set_default_value(\$distinfo_file, "distinfo"); + $filesdir = expand_variable($whole, "FILESDIR"); + set_default_value(\$filesdir, "files"); + $patchdir = expand_variable($whole, "PATCHDIR"); + set_default_value(\$patchdir, "patches"); + + log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] DISTINFO_FILE=$distinfo_file"); + log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] FILESDIR=$filesdir"); + log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] PATCHDIR=$patchdir"); + log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] PKGDIR=$pkgdir"); + + ${$ref_whole} = $whole; + ${$ref_lines} = $lines; + return true; +} + # -# Procedures to check a single line, part 3. +# Subroutines to check a single line. # +sub checkline_length($$) { + my ($line, $maxlength) = @_; + + if (length($line->text) > $maxlength) { + $line->log_warning("Line too long (should be no more than $maxlength characters)."); + $line->explain( + "Back in the old time, terminals with 80x25 characters were common.", + "And this is still the default size of many terminal emulators.", + "Moderately short lines also make reading easier."); + } +} + +sub checkline_valid_characters($$) { + my ($line, $re_validchars) = @_; + my ($rest); + + ($rest = $line->text) =~ s/$re_validchars//g; + if ($rest ne "") { + my @chars = map { $_ = sprintf("0x%02x", ord($_)); } split(//, $rest); + $line->log_warning(sprintf("Line contains invalid characters (%s).", join(", ", @chars))); + } +} + +sub checkline_valid_characters_in_variable($$) { + my ($line, $re_validchars) = @_; + my ($varname, $rest); + + $rest = $line->text; + if ($rest =~ regex_varassign) { + ($varname, undef, $rest) = ($1, $2, $3); + } else { + return; + } + + $rest =~ s/$re_validchars//g; + if ($rest ne "") { + my @chars = map { $_ = sprintf("0x%02x", ord($_)); } split(//, $rest); + $line->log_warning(sprintf("${varname} contains invalid characters (%s).", join(", ", @chars))); + } +} + +sub checkline_trailing_whitespace($) { + my ($line) = @_; + if ($line->text =~ /\s+$/) { + $line->log_note("Trailing white-space."); + } +} + +sub checkline_rcsid_regex($$$) { + my ($line, $prefix_regex, $prefix) = @_; + my ($id) = ($opt_rcsidstring . ($is_wip ? "|Id" : "")); + + if ($line->text !~ qr"^${prefix_regex}\$(${id})(?::[^\$]*|)\$$") { + $line->log_error("\"${prefix}\$${opt_rcsidstring}\$\" expected."); + return false; + } + return true; +} + +sub checkline_rcsid($$) { + my ($line, $prefix) = @_; + checkline_rcsid_regex($line, quotemeta($prefix), $prefix); +} + +sub checkline_relative_path($$) { + my ($line, $path) = @_; + + if (!$is_wip && $path =~ qr"/wip/") { + $line->log_error("A pkgsrc package must not depend on any outside package."); + } + $path = resolve_relative_path($path); + if ($path =~ regex_unresolved) { + $line->log_info("Unresolved path: \"${path}\"."); + } elsif (!-e "${current_dir}/${path}") { + $line->log_error("\"${path}\" does not exist."); + } +} + sub checkline_mk_direct_tool_use($$$) { my ($line, $text, $where) = @_; my ($rest, $vartools); @@ -1921,7 +1578,7 @@ sub checkline_mk_varassign($$$$$) { checkline_mk_vartype($line, $varname, $op, $value, $comment); } -sub checkline_basic_vartype($$$$$) { +sub checkline_mk_vartype_basic($$$$$) { my ($line, $varname, $type, $value, $comment) = @_; my ($value_novar); @@ -2293,7 +1950,7 @@ sub checkline_mk_vartype($$$$$) { foreach my $word (@words) { if (defined($element_type)) { - checkline_basic_vartype($line, $varname, $element_type, $word, $comment); + checkline_mk_vartype_basic($line, $varname, $element_type, $word, $comment); } } @@ -2302,15 +1959,89 @@ sub checkline_mk_vartype($$$$$) { } } else { - checkline_basic_vartype($line, $varname, $type, $value, $comment); + checkline_mk_vartype_basic($line, $varname, $type, $value, $comment); } } # -# Procedures to check an array of lines, part 3. +# Procedures to check an array of lines. # -sub checklines_deprecated_variables($) { +sub checklines_trailing_empty_lines($) { + my ($lines) = @_; + my ($last, $max); + + $max = $#{$lines} + 1; + for ($last = $max; $last > 1 && $lines->[$last - 1]->text eq ""; ) { + $last--; + } + if ($last != $max) { + $lines->[$last]->log_note("Trailing empty lines."); + } +} + +sub checklines_multiple_patches($) { + my ($lines) = @_; + my ($files_in_patch, $patch_state, $line_type, $dellines); + + $files_in_patch = 0; + $patch_state = ""; + $dellines = 0; + foreach my $line (@{$lines}) { + my $text = $line->text; + + if ($text =~ qr"^@@ -\d+,(\d+) \+\d+,\d+ @@") { + $line_type = ""; + $dellines = $1; + + } elsif ($dellines == 0 && index($text, "--- ") == 0 && $text !~ qr"^--- \d+(?:,\d+|) ----$") { + $line_type = "-"; + + } elsif (index($text, "*** ") == 0 && $text !~ qr"^\*\*\* \d+(?:,\d+|) \*\*\*\*$") { + $line->log_warning("Please use unified diffs (diff -u) for patches."); + $line_type = "*"; + + } elsif (index($text, "+++ ") == 0) { + $line_type = "+"; + + } elsif ($dellines > 0 && $text =~ qr"^(?:-|\s)") { + $line_type = ""; + $dellines--; + + } else { + $line_type = ""; + } + + if ($patch_state eq "*") { + if ($line_type eq "-") { + $files_in_patch++; + $patch_state = ""; + } else { + $line->log_error("[internal] Unknown patch format."); + } + + } elsif ($patch_state eq "-") { + if ($line_type eq "+") { + $files_in_patch++; + $patch_state = ""; + } else { + $line->log_error("[internal] Unknown patch format."); + } + + } elsif ($patch_state eq "") { + $patch_state = $line_type; + } + } + + if ($files_in_patch > 1) { + log_warning($lines->[0]->file, NO_LINE_NUMBER, "Contains patches for $files_in_patch files, should be only one."); + + } elsif ($files_in_patch == 0) { + log_error($lines->[0]->file, NO_LINE_NUMBER, "Contains no patch."); + } +} + +sub checklines_mk_deprecated_variables($) { my ($lines) = @_; my ($vars, $varnames, $regex_varuse); @@ -2363,7 +2094,7 @@ sub checklines_mk($) { } } - checklines_deprecated_variables($lines); + checklines_mk_deprecated_variables($lines); autofix($lines); } @@ -2767,104 +2498,147 @@ sub checklines_package_Makefile($) { } # -# Miscellaneous procedures. +# Procedures to check a single file. # -sub expand_variable($$) { - my ($whole, $varname) = @_; - my ($value, $re); +sub checkfile_ALTERNATIVES($) { + my ($fname) = @_; + my ($lines); - $re = qr"\n${varname}([+:?]?)=[ \t]*([^\n#]*)"; - $value = undef; - while ($whole =~ m/$re/g) { - my ($op, $val) = ($1, $2); - if ($op ne "?" || !defined($value)) { - $value = $val; - } + log_info($fname, NO_LINE_NUMBER, "[checkfile_ALTERNATIVES]"); + + checkperms($fname); + if (!($lines = load_file($fname))) { + log_error($fname, NO_LINE_NUMBER, "Cannot be read."); + return; } - if (!defined($value)) { - return undef; +} + +sub checkfile_DESCR($) { + my ($fname) = @_; + my ($maxchars, $maxlines) = (80, 24); + my ($descr); + + log_info($fname, NO_LINE_NUMBER, "[checkfile_DESCR]"); + + checkperms($fname); + if (!($descr = load_file($fname))) { + log_error($fname, NO_LINE_NUMBER, "Cannot be read."); + return; + } + if (@{$descr} == 0) { + log_error($fname, NO_LINE_NUMBER, "Must not be empty."); + return; } - $value = resolve_relative_path($value); - if ($value =~ regex_unresolved) { - log_info(NO_FILE, NO_LINE_NUMBER, "[expand_variable] The variable ${varname} could not be resolved completely. Its value is \"${value}\"."); + foreach my $line (@{$descr}) { + checkline_length($line, $maxchars); + checkline_trailing_whitespace($line); + checkline_valid_characters($line, regex_validchars); } - return $value; -} + checklines_trailing_empty_lines($descr); -sub set_default_value($$) { - my ($varref, $value) = @_; + if (@{$descr} > $maxlines) { + my $line = $descr->[$maxlines]; - if (!defined(${$varref}) || ${$varref} =~ regex_unresolved) { - ${$varref} = $value; + $line->log_warning("File too long (should be no more than $maxlines lines)."); + $line->explain( + "A common terminal size is 80x25 characters. The DESCR file should", + "fit on one screen. It is also intended to give a _brief_ summary", + "about the package's contents."); } } -# -# Loading data from package-specific files, part 2. -# - -sub load_package_Makefile($$$) { - my ($subr) = "load_package_Makefile"; - my ($fname, $ref_whole, $ref_lines) = @_; - my ($whole, $lines, $all_lines); +sub checkfile_distinfo($) { + my ($fname) = @_; + my ($lines, %in_distinfo, %sums); - log_info($fname, NO_LINE_NUMBER, "Checking package Makefile."); + log_info($fname, NO_LINE_NUMBER, "[checkfile_distinfo]"); - $whole = readmakefile($fname, $lines = [], $all_lines = [], {}); - if (!$whole) { + checkperms($fname); + if (!($lines = load_file($fname))) { log_error($fname, NO_LINE_NUMBER, "Cannot be read."); - return false; + return; } - if ($opt_dumpmakefile) { - print("OK: whole Makefile (with all included files) follows:\n"); - foreach my $line (@{$all_lines}) { - printf("%s\n", $line->to_string()); + if (@{$lines} == 0) { + log_error($fname, NO_LINE_NUMBER, "Must not be empty."); + return; + } + + checkline_rcsid($lines->[0], ""); + if (1 <= $#{$lines} && $lines->[1]->text ne "") { + $lines->[1]->log_warning("Empty line expected."); + $lines->[1]->explain("This is merely for aesthetical purposes."); + } + + foreach my $line (@{$lines}[2..$#{$lines}]) { + if ($line->text !~ /^(MD5|SHA1|RMD160|Size) \(([^)]+)\) = (.*)(?: bytes)?$/) { + $line->log_error("Unknown line type."); + next; } + + my ($alg, $file, $sum) = ($1, $2, $3); + + if ($file =~ /^patch-[A-Za-z0-9]+$/) { + if (open(PATCH, "< ${current_dir}/${patchdir}/${file}")) { + my $data = ""; + foreach my $patchline (<PATCH>) { + $data .= $patchline unless $patchline =~ qr"\$NetBSD.*\$"; + } + close(PATCH); + my $chksum = Digest::SHA1::sha1_hex($data); + if ($sum ne $chksum) { + $line->log_error("${alg} checksum of $file differs (expected ${sum}, got ${chksum}). Rerun '".conf_make." makepatchsum'."); + } + } elsif (!$hack_php_patches) { + $line->log_warning("$file does not exist."); + $line->explain( + "All patches that are mentioned in a distinfo file should actually exist.", + "What's the use of a checksum if there is no file to check?"); + } + } else { + $sums{$alg}->{$file} = $line; + } + $in_distinfo{$file} = true; } + checklines_trailing_empty_lines($lines); - # HACK - if ($whole =~ qr"\nPHPEXT_MK" && $whole !~ qr"\nUSE_PHP_EXT_PATCHES") { - log_info($fname, NO_LINE_NUMBER, "[hack] USE_PHP_EXT_PATCHES"); - $whole =~ s,\nPATCHDIR=.*PHPPKGSRCDIR.*,,; - $hack_php_patches = true; + # Check for distfiles that have SHA1, but not RMD160 checksums + foreach my $sha1_file (sort(keys(%{$sums{"SHA1"}}))) { + if (!exists($sums{"RMD160"}->{$sha1_file})) { + $sums{"SHA1"}->{$sha1_file}->log_error("RMD160 checksum missing for \"${sha1_file}\"."); + } } - # HACK - if ($whole =~ qr"\nPECL_VERSION") { - log_info($fname, NO_LINE_NUMBER, "[hack] PECL_VERSION"); - $whole =~ s,\nDISTINFO_FILE=.*PHPPKGSRCDIR.*,,; + + foreach my $patch (<${current_dir}/$patchdir/patch-*>) { + $patch = basename($patch); + if (!exists($in_distinfo{$patch})) { + log_error($fname, NO_LINE_NUMBER, "$patch is not recorded. Rerun '".conf_make." makepatchsum'."); + } } +} - $pkgdir = expand_variable($whole, "PKGDIR"); - set_default_value(\$pkgdir, "."); - $distinfo_file = expand_variable($whole, "DISTINFO_FILE"); - set_default_value(\$distinfo_file, "distinfo"); - $filesdir = expand_variable($whole, "FILESDIR"); - set_default_value(\$filesdir, "files"); - $patchdir = expand_variable($whole, "PATCHDIR"); - set_default_value(\$patchdir, "patches"); +sub checkfile_extra($) { + my ($fname) = @_; + my ($lines); - log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] DISTINFO_FILE=$distinfo_file"); - log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] FILESDIR=$filesdir"); - log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] PATCHDIR=$patchdir"); - log_info(NO_FILE, NO_LINE_NUMBER, "[${subr}] PKGDIR=$pkgdir"); + log_info($fname, NO_LINE_NUMBER, "[checkfile_extra]"); - ${$ref_whole} = $whole; - ${$ref_lines} = $lines; - return true; + $lines = load_file($fname); + if (!$lines) { + log_error($fname, NO_LINE_NUMBER, "Could not be read."); + return; + } + checklines_trailing_empty_lines($lines); + checkperms($fname); } -# -# Procedures to check a file, part 3. -# - -sub checkfile_ALTERNATIVES($) { +sub checkfile_INSTALL($) { my ($fname) = @_; my ($lines); - log_info($fname, NO_LINE_NUMBER, "[checkfile_ALTERNATIVES]"); + log_info($fname, NO_LINE_NUMBER, "[checkfile_INSTALL]"); checkperms($fname); if (!($lines = load_file($fname))) { @@ -2873,17 +2647,44 @@ sub checkfile_ALTERNATIVES($) { } } -sub checkfile_INSTALL($) { +sub checkfile_MESSAGE($) { my ($fname) = @_; - my ($lines); + my ($message); - log_info($fname, NO_LINE_NUMBER, "[checkfile_INSTALL]"); + my @explanation = ( + "A MESSAGE file should consist of a header line, having 75 \"=\"", + "characters, followed by a line containing only the RCS Id, then an", + "empty line, your text and finally the footer line, which is the", + "same as the header line."); + + log_info($fname, NO_LINE_NUMBER, "[checkfile_MESSAGE]"); checkperms($fname); - if (!($lines = load_file($fname))) { + if (!($message = load_file($fname))) { log_error($fname, NO_LINE_NUMBER, "Cannot be read."); return; } + + if (@{$message} < 3) { + log_warning($fname, NO_LINE_NUMBER, "File too short."); + explain($fname, NO_LINE_NUMBER, @explanation); + return; + } + if ($message->[0]->text ne "=" x 75) { + $message->[0]->log_warning("Expected a line of exactly 75 \"=\" characters."); + explain($fname, NO_LINE_NUMBER, @explanation); + } + checkline_rcsid($message->[1], ""); + foreach my $line (@{$message}) { + checkline_length($line, 80); + checkline_trailing_whitespace($line); + checkline_valid_characters($line, regex_validchars); + } + if ($message->[-1]->text ne "=" x 75) { + $message->[-1]->log_warning("Expected a line of exactly 75 \"=\" characters."); + explain($fname, NO_LINE_NUMBER, @explanation); + } + checklines_trailing_empty_lines($message); } sub checkfile_mk($) { @@ -2975,6 +2776,177 @@ sub checkfile_package_Makefile($$$) { autofix($lines); } +sub checkfile_patches_patch($) { + my ($fname) = @_; + my ($lines); + + log_info($fname, NO_LINE_NUMBER, "[checkfile_patches_patch]"); + + checkperms($fname); + if (!($lines = load_file($fname))) { + log_error($fname, NO_LINE_NUMBER, "Could not be read."); + return; + } + if (@{$lines} == 0) { + log_error($fname, NO_LINE_NUMBER, "Must not be empty."); + return; + } + checkline_rcsid($lines->[0], ""); + + foreach my $line (@{$lines}[1..$#{$lines}]) { + if ($line->text =~ qr"\$(Author|Date|Header|Id|Locker|Log|Name|RCSfile|Revision|Source|State|$opt_rcsidstring)[:\$]") { + my ($tag) = ($1); + if ($line->text =~ qr"^(\@\@.*?\@\@)") { + $line->log_warning("Patches should not contain RCS tags."); + $line->set_text($1); + } else { + $line->log_warning("Possible RCS tag \"\$${tag}\$\". Please remove it by reducing the number of context lines using pkgdiff or \"diff -U[210]\"."); + } + } + + if ($line->text =~ qr"^\+") { + use constant good_macros => PkgLint::Util::array_to_hash(qw( + __STDC__ + + __GNUC__ __GNUC_MINOR__ + __SUNPRO_C + + __i386 + __mips + __sparc + + __DragonFly__ + __FreeBSD__ + __INTERIX + __linux__ + __NetBSD__ + __OpenBSD__ + __SVR4 + __sun + + __GLIBC__ + )); + use constant bad_macros => { + "__sparc__" => "__sparc", + "__sun__" => "__sun", + "__svr4__" => "__SVR4", + }; + my $rest = $line->text; + my $re_ifdef = qr"defined\((__[\w_]+)\)"; + + while ($rest =~ s/$re_ifdef//) { + my ($macro) = ($1); + + if (exists(good_macros->{$macro})) { + $line->log_debug("Found good macro \"${macro}\"."); + } elsif (exists(bad_macros->{$macro})) { + $line->log_warning("The macro \"${macro}\" is unportable. Please use \"".bad_macros->{$macro}."\" instead."); + $line->explain("See the pkgsrc guide, section \"CPP defines\" for details."); + } else { + $line->log_info("Found unknown macro \"${macro}\"."); + } + } + } + } + checklines_trailing_empty_lines($lines); + + checklines_multiple_patches($lines); +} + +sub checkfile_PLIST($) { + my ($fname) = @_; + my ($plist, $last_file_seen); + + log_info($fname, NO_LINE_NUMBER, "[checkfile_PLIST]"); + + checkperms($fname); + if (!($plist = load_file($fname))) { + log_error($fname, NO_LINE_NUMBER, "Cannot be read."); + return; + } + if (@{$plist} == 0) { + log_error($fname, NO_LINE_NUMBER, "Must not be empty."); + return; + } + checkline_rcsid($plist->[0], "\@comment "); + + line: + foreach my $line (@{$plist}) { + my $text = $line->text; + + checkline_trailing_whitespace($line); + + if ($text =~ /^\@([a-z]+)\s+(.*)/) { + my ($cmd, $arg) = ($1, $2); + + if ($cmd eq "unexec" && $arg =~ qr"^(rmdir|\$\{RMDIR\} \%D/)(.*)") { + my ($rmdir, $rest) = ($1, $2); + if ($rest !~ qr"(?:true|\$\{TRUE\})") { + $line->log_warning("Please use \"\@dirrm\" instead of \"\@unexec rmdir\"."); + } + + } elsif (($cmd eq "exec" || $cmd eq "unexec")) { + if ($arg =~ /(?:install-info|\$\{INSTALL_INFO\})/) { + $line->log_warning("\@exec/unexec install-info is deprecated."); + + } elsif ($arg =~ /ldconfig/ && $arg !~ qr"/usr/bin/true") { + $line->log_error("ldconfig must be used with \"||/usr/bin/true\"."); + } + + } elsif ($cmd eq "comment" || $cmd eq "dirrm") { + # nothing to do + + } else { + $line->log_warning("Unknown PLIST directive \"\@$cmd\"."); + } + + } elsif ($text =~ qr"^[A-Za-z0-9\$]") { + if ($opt_warn_plist_sort && $text =~ qr"^\w" && $text !~ regex_unresolved) { + if (defined($last_file_seen)) { + if ($last_file_seen gt $text) { + $line->log_warning("${text} should be sorted before ${last_file_seen}."); + } + } + $last_file_seen = $text; + } + + if ($text =~ qr"^doc/") { + $line->log_error("Documentation must be installed under share/doc, not doc."); + + } elsif ($text =~ qr"^etc/rc\.d/") { + $line->log_error("RCD_SCRIPTS must not be registered in the PLIST. Please use the RCD_SCRIPTS framework."); + + } elsif ($text =~ qr"^etc/") { + $line->log_error("Configuration files must not be registered in the PLIST. Please use the CONF_FILES framework, which is described in mk/install/bsd.pkginstall.mk."); + + } elsif ($text eq "info/dir") { + $line->log_error("\"info/dir\" must not be listed. Use install-info to add/remove an entry."); + + } elsif ($text =~ qr"^lib/locale/") { + $line->log_error("\"lib/locale\" must not be listed. Use \${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead."); + + } elsif ($text =~ qr"^share/locale/") { + $line->log_warning("Use of \"share/locale\" is deprecated. Use \${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead."); + + } elsif ($text =~ qr"^share/man/") { + $line->log_warning("Man pages should be installed into man/, not share/man/."); + } + + if ($text =~ /\${PKGLOCALEDIR}/ && defined($makevar) && !exists($makevar->{"USE_PKGLOCALEDIR"})) { + $line->log_warning("PLIST contains \${PKGLOCALEDIR}, but USE_PKGLOCALEDIR was not found."); + } + + if ($text =~ qr"/CVS/") { + $line->log_warning("CVS files should not be in the PLIST."); + } + + } else { + $line->log_error("Unknown line type."); + } + } + checklines_trailing_empty_lines($plist); +} + sub checkfile($) { my ($fname) = @_; my ($st, $basename); |