summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint4/files
diff options
context:
space:
mode:
authorrillig <rillig>2015-11-25 16:42:21 +0000
committerrillig <rillig>2015-11-25 16:42:21 +0000
commit8faab4b446726e656342a21d5246105e0c1e91fe (patch)
tree0ac4f352fef7f807bff85bb9861afe2b69c21513 /pkgtools/pkglint4/files
parent872b4d907062f33b5b1c60d604a1d3e91225af41 (diff)
downloadpkgsrc-8faab4b446726e656342a21d5246105e0c1e91fe.tar.gz
Reimported pkglint-4.518 from pkgtools/pkglint as pkglint4
The Perl version of pkglint (pkglint<5.0) runs on all platforms that are supported by pkgsrc. Not so the Go version (pkglint>=5.0). To support development of packages on all platforms, this version is provided, and it will be supported equally. Its output differs a bit from pkglint>=5.0, but the basic checks are the same.
Diffstat (limited to 'pkgtools/pkglint4/files')
-rw-r--r--pkgtools/pkglint4/files/PkgLint/CVS_Entry.pm21
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Change.pm21
-rw-r--r--pkgtools/pkglint4/files/PkgLint/FileUtil.pm166
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Line.pm217
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Logging.pm134
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Patches.pm639
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Shell.pm708
-rw-r--r--pkgtools/pkglint4/files/PkgLint/SimpleMatch.pm44
-rw-r--r--pkgtools/pkglint4/files/PkgLint/SubstContext.pm198
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Type.pm102
-rw-r--r--pkgtools/pkglint4/files/PkgLint/Util.pm97
-rw-r--r--pkgtools/pkglint4/files/PkgLint/VarUseContext.pm67
-rw-r--r--pkgtools/pkglint4/files/build.pl25
-rw-r--r--pkgtools/pkglint4/files/deprecated.map166
-rw-r--r--pkgtools/pkglint4/files/doc/Makefile27
-rw-r--r--pkgtools/pkglint4/files/doc/chap.code.xml307
-rw-r--r--pkgtools/pkglint4/files/doc/chap.defs.xml26
-rw-r--r--pkgtools/pkglint4/files/doc/chap.design.xml117
-rw-r--r--pkgtools/pkglint4/files/doc/chap.future.xml91
-rw-r--r--pkgtools/pkglint4/files/doc/chap.intro.xml15
-rw-r--r--pkgtools/pkglint4/files/doc/chap.statemachines.xml77
-rw-r--r--pkgtools/pkglint4/files/doc/chap.types.xml545
-rw-r--r--pkgtools/pkglint4/files/doc/pkglint.xml38
-rw-r--r--pkgtools/pkglint4/files/doc/statemachine.patch.diabin0 -> 2385 bytes
-rw-r--r--pkgtools/pkglint4/files/doc/statemachine.shellcmd.diabin0 -> 3531 bytes
-rw-r--r--pkgtools/pkglint4/files/doc/stylesheet.xsl6
-rw-r--r--pkgtools/pkglint4/files/makevars.map768
-rw-r--r--pkgtools/pkglint4/files/pkglint.0213
-rw-r--r--pkgtools/pkglint4/files/pkglint.1244
-rw-r--r--pkgtools/pkglint4/files/pkglint.pl5915
-rw-r--r--pkgtools/pkglint4/files/pkglint.t198
-rw-r--r--pkgtools/pkglint4/files/plist-clash.pl60
32 files changed, 11252 insertions, 0 deletions
diff --git a/pkgtools/pkglint4/files/PkgLint/CVS_Entry.pm b/pkgtools/pkglint4/files/PkgLint/CVS_Entry.pm
new file mode 100644
index 00000000000..3fe7559fd65
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/CVS_Entry.pm
@@ -0,0 +1,21 @@
+# $NetBSD: CVS_Entry.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# One line from a CVS/Entries file.
+#
+package PkgLint::CVS_Entry;
+
+use strict;
+use warnings;
+
+use enum qw(FNAME REVISION MTIME TAG);
+
+sub new($$$$$) {
+ my ($class, $fname, $revision, $date, $tag) = @_;
+ my $self = [ $fname, $revision, $date, $tag ];
+ bless($self, $class);
+ return $self;
+}
+sub fname($) { return shift()->[FNAME]; }
+sub revision($) { return shift()->[REVISION]; }
+sub mtime($) { return shift()->[MTIME]; }
+sub tag($) { return shift()->[TAG]; }
diff --git a/pkgtools/pkglint4/files/PkgLint/Change.pm b/pkgtools/pkglint4/files/PkgLint/Change.pm
new file mode 100644
index 00000000000..89368c465fe
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Change.pm
@@ -0,0 +1,21 @@
+# $NetBSD: Change.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# A change entry from doc/CHANGES-*
+#
+package PkgLint::Change;
+
+use strict;
+use warnings;
+
+sub new($$$$$$) {
+ my ($class, $line, $action, $pkgpath, $version, $author, $date) = @_;
+ my $self = [ $line, $action, $pkgpath, $version, $author, $date ];
+ bless($self, $class);
+ return $self;
+}
+sub line($) { return shift()->[0]; }
+sub action($) { return shift()->[1]; }
+sub pkgpath($) { return shift()->[2]; }
+sub version($) { return shift()->[3]; }
+sub author($) { return shift()->[4]; }
+sub date($) { return shift()->[5]; }
diff --git a/pkgtools/pkglint4/files/PkgLint/FileUtil.pm b/pkgtools/pkglint4/files/PkgLint/FileUtil.pm
new file mode 100644
index 00000000000..f29c2bdccc7
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/FileUtil.pm
@@ -0,0 +1,166 @@
+# $NetBSD: FileUtil.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# Subroutines for loading and saving line-oriented files.
+# The load_file() subroutine loads a file completely into memory,
+# optionally handling continuation line folding. The load_lines() subrou-
+# tine is an abbreviation for the common case of loading files without
+# continuation lines. The save_autofix_changes() subroutine examines an
+# array of lines if some of them have changed. It then saves the modified
+# files.
+#
+package PkgLint::FileUtil;
+
+use strict;
+use warnings;
+
+BEGIN {
+ use Exporter;
+ use vars qw(@ISA @EXPORT_OK);
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ load_file load_lines
+ save_autofix_changes
+ );
+
+ import PkgLint::Util qw(
+ false true
+ );
+ import PkgLint::Logging qw(
+ NO_LINE_NUMBER
+ log_error log_note
+ );
+}
+
+sub load_physical_lines($) {
+ my ($fname) = @_;
+ my ($physlines, $line, $lineno);
+
+ $physlines = [];
+ open(my $f, "<", $fname) or return;
+ $lineno = 0;
+ while (defined($line = <$f>)) {
+ $lineno++;
+ push(@{$physlines}, [$lineno, $line]);
+ }
+ close($f) or return;
+ return $physlines;
+}
+
+sub get_logical_line($$$) {
+ my ($fname, $lines, $ref_lineno) = @_;
+ my ($value, $lineno, $first, $firstlineno, $lastlineno, $physlines);
+
+ $value = "";
+ $first = true;
+ $lineno = ${$ref_lineno};
+ $firstlineno = $lines->[$lineno]->[0];
+ $physlines = [];
+
+ for (; $lineno <= $#{$lines}; $lineno++) {
+ if ($lines->[$lineno]->[1] =~ m"^([ \t]*)(.*?)([ \t]*)(\\?)\n?$") {
+ my ($indent, $text, $outdent, $cont) = ($1, $2, $3, $4);
+
+ if ($first) {
+ $value .= $indent;
+ $first = false;
+ }
+
+ $value .= $text;
+ push(@{$physlines}, $lines->[$lineno]);
+
+ if ($cont eq "\\") {
+ $value .= " ";
+ } else {
+ $value .= $outdent;
+ last;
+ }
+ }
+ }
+
+ if ($lineno > $#{$lines}) {
+ # The last line in the file is a continuation line
+ $lineno--;
+ }
+ $lastlineno = $lines->[$lineno]->[0];
+ ${$ref_lineno} = $lineno + 1;
+
+ return PkgLint::Line->new($fname,
+ $firstlineno == $lastlineno
+ ? $firstlineno
+ : "$firstlineno--$lastlineno",
+ $value,
+ $physlines);
+}
+
+sub load_lines($$) {
+ my ($fname, $fold_backslash_lines) = @_;
+ my ($physlines, $seen_newline, $loglines);
+
+ $physlines = load_physical_lines($fname);
+ if (!$physlines) {
+ return false;
+ }
+
+ $seen_newline = true;
+ $loglines = [];
+ if ($fold_backslash_lines) {
+ for (my $lineno = 0; $lineno <= $#{$physlines}; ) {
+ push(@{$loglines}, get_logical_line($fname, $physlines, \$lineno));
+ }
+ } else {
+ foreach my $physline (@{$physlines}) {
+ my $text = $physline->[1];
+
+ $text =~ s/\n$//;
+ push(@{$loglines}, PkgLint::Line->new($fname, $physline->[0], $text, [$physline]));
+ }
+ }
+
+ if (0 <= $#{$physlines} && $physlines->[-1]->[1] !~ m"\n$") {
+ log_error($fname, $physlines->[-1]->[0], "File must end with a newline.");
+ }
+
+ return $loglines;
+}
+
+sub load_file($) {
+ my ($fname) = @_;
+
+ return load_lines($fname, false);
+}
+
+sub save_autofix_changes($) {
+ my ($lines) = @_;
+
+ my (%changed, %physlines);
+
+ foreach my $line (@{$lines}) {
+ if ($line->is_changed) {
+ $changed{$line->fname}++;
+ }
+ push(@{$physlines{$line->fname}}, @{$line->physlines});
+ }
+
+ foreach my $fname (sort(keys(%changed))) {
+ my $new = "${fname}.pkglint.tmp";
+ my $f;
+
+ if (!open($f, ">", $new)) {
+ log_error($new, NO_LINE_NUMBER, "$!");
+ next;
+ }
+ foreach my $physline (@{$physlines{$fname}}) {
+ print $f ($physline->[1]);
+ }
+ if (!close($f)) {
+ log_error($new, NO_LINE_NUMBER, "$!");
+ next;
+ }
+
+ if (!rename($new, $fname)) {
+ log_error($fname, NO_LINE_NUMBER, "$!");
+ next;
+ }
+ log_note($fname, NO_LINE_NUMBER, "Has been autofixed. Please re-run pkglint.");
+ }
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/Line.pm b/pkgtools/pkglint4/files/PkgLint/Line.pm
new file mode 100644
index 00000000000..cc93af4f59e
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Line.pm
@@ -0,0 +1,217 @@
+# $NetBSD: Line.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# When files are read in by pkglint, they are interpreted in terms of
+# lines. For Makefiles, line continuations are handled properly, allowing
+# multiple physical lines to end in a single logical line. For other files
+# there is a 1:1 translation.
+#
+# A difference between the physical and the logical lines is that the
+# physical lines include the line end sequence, whereas the logical lines
+# do not.
+#
+# A logical line is a class having the read-only fields C<file>,
+# C<lines>, C<text>, C<physlines> and C<is_changed>, as well as some
+# methods for printing diagnostics easily.
+#
+# Some other methods allow modification of the physical lines, but leave
+# the logical line (the C<text>) untouched. These methods are used in the
+# --autofix mode.
+#
+# A line can have some "extra" fields that allow the results of parsing to
+# be saved under a name.
+#
+package PkgLint::Line;
+
+use strict;
+use warnings;
+
+BEGIN {
+ import PkgLint::Util qw(
+ false true
+ assert
+ );
+}
+
+use enum qw(FNAME LINES TEXT PHYSLINES CHANGED BEFORE AFTER EXTRA);
+
+sub new($$$$) {
+ my ($class, $fname, $lines, $text, $physlines) = @_;
+ my ($self) = ([$fname, $lines, $text, $physlines, false, [], [], {}]);
+ bless($self, $class);
+ return $self;
+}
+
+sub fname($) { return shift()->[FNAME]; }
+sub lines($) { return shift()->[LINES]; }
+sub text($) { return shift()->[TEXT]; }
+# Note: physlines is _not_ a simple getter method.
+sub is_changed($) { return shift()->[CHANGED]; }
+
+# querying, getting and setting the extra values.
+sub has($$) {
+ my ($self, $name) = @_;
+ return exists($self->[EXTRA]->{$name});
+}
+sub get($$) {
+ my ($self, $name) = @_;
+ assert(false, "Field ${name} does not exist.")
+ unless exists($self->[EXTRA]->{$name});
+ return $self->[EXTRA]->{$name};
+}
+sub set($$$) {
+ my ($self, $name, $value) = @_;
+ assert(false, "Field ${name} already exists.")
+ if exists($self->[EXTRA]->{$name});
+
+ # Make sure that the line does not become a cyclic data structure.
+ my $type = ref($value);
+ if ($type eq "") {
+ # ok
+ } elsif ($type eq "ARRAY") {
+ foreach my $element (@{$value}) {
+ my $element_type = ref($element);
+ assert(false, "Invalid array data type: name=${name}, type=${element_type}.")
+ unless $element_type eq "" || $element_type eq "PkgLint::SimpleMatch";
+ }
+ } else {
+ assert(false, "Invalid data: name=${name}, value=${value}.");
+ }
+
+ $self->[EXTRA]->{$name} = $value;
+}
+
+sub physlines($) {
+ my ($self) = @_;
+ return [@{$self->[BEFORE]}, @{$self->[PHYSLINES]}, @{$self->[AFTER]}];
+}
+
+# Only for PkgLint::String support
+sub substring($$$$) {
+ my ($self, $line, $start, $end) = @_;
+
+ return substr($self->[PHYSLINES]->[$line]->[1], $start, $end);
+}
+
+sub show_source($$) {
+ my ($self, $out) = @_;
+
+ if (PkgLint::Logging::get_show_source_flag()) {
+ print $out ("\n");
+ foreach my $line (@{$self->physlines}) {
+ print $out ("> " . $line->[1]);
+ }
+ }
+}
+
+sub log_fatal($$) {
+ my ($self, $text) = @_;
+
+ $self->show_source(*STDERR);
+ PkgLint::Logging::log_fatal($self->fname, $self->[LINES], $text);
+}
+sub log_error($$) {
+ my ($self, $text) = @_;
+
+ $self->show_source(*STDOUT);
+ PkgLint::Logging::log_error($self->fname, $self->[LINES], $text);
+}
+sub log_warning($$) {
+ my ($self, $text) = @_;
+
+ $self->show_source(*STDOUT);
+ PkgLint::Logging::log_warning($self->fname, $self->[LINES], $text);
+}
+sub log_note($$) {
+ my ($self, $text) = @_;
+
+ $self->show_source(*STDOUT);
+ PkgLint::Logging::log_note($self->fname, $self->[LINES], $text);
+}
+sub log_debug($$) {
+ my ($self, $text) = @_;
+
+ $self->show_source(*STDOUT);
+ PkgLint::Logging::log_debug($self->fname, $self->[LINES], $text);
+}
+sub explain_error($@) {
+ my ($self, @texts) = @_;
+
+ PkgLint::Logging::explain_error($self->fname, $self->[LINES], @texts);
+}
+sub explain_warning($@) {
+ my ($self, @texts) = @_;
+
+ PkgLint::Logging::explain_warning($self->fname, $self->[LINES], @texts);
+}
+sub explain_note($@) {
+ my ($self, @texts) = @_;
+
+ PkgLint::Logging::explain_note($self->fname, $self->[LINES], @texts);
+}
+sub explain_info($@) {
+ my ($self, @texts) = @_;
+
+ PkgLint::Logging::explain_info($self->fname, $self->[LINES], @texts);
+}
+
+sub to_string($) {
+ my ($self) = @_;
+
+ return $self->fname . ":" . $self->[LINES] . ": " . $self->[TEXT];
+}
+
+sub prepend_before($$) {
+ my ($self, $text) = @_;
+
+ unshift(@{$self->[BEFORE]}, [0, "$text\n"]);
+ $self->[CHANGED] = true;
+}
+sub append_before($$) {
+ my ($self, $text) = @_;
+
+ push(@{$self->[BEFORE]}, [0, "$text\n"]);
+ $self->[CHANGED] = true;
+}
+sub prepend_after($$) {
+ my ($self, $text) = @_;
+
+ unshift(@{$self->[AFTER]}, [0, "$text\n"]);
+ $self->[CHANGED] = true;
+}
+sub append_after($$) {
+ my ($self, $text) = @_;
+
+ push(@{$self->[AFTER]}, [0, "$text\n"]);
+ $self->[CHANGED] = true;
+}
+sub delete($) {
+ my ($self) = @_;
+
+ $self->[PHYSLINES] = [];
+ $self->[CHANGED] = true;
+}
+sub replace($$$) {
+ my ($self, $from, $to) = @_;
+ my $phys = $self->[PHYSLINES];
+
+ foreach my $i (0..$#{$phys}) {
+ if ($phys->[$i]->[0] != 0 && $phys->[$i]->[1] =~ s/\Q$from\E/$to/g) {
+ $self->[CHANGED] = true;
+ }
+ }
+}
+sub replace_regex($$$) {
+ my ($self, $from_re, $to) = @_;
+ my $phys = $self->[PHYSLINES];
+
+ foreach my $i (0..$#{$phys}) {
+ if ($phys->[$i]->[0] != 0 && $phys->[$i]->[1] =~ s/$from_re/$to/) {
+ $self->[CHANGED] = true;
+ }
+ }
+}
+sub set_text($$) {
+ my ($self, $text) = @_;
+ $self->[PHYSLINES] = [[0, "$text\n"]];
+ $self->[CHANGED] = true;
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/Logging.pm b/pkgtools/pkglint4/files/PkgLint/Logging.pm
new file mode 100644
index 00000000000..eecb17a6a59
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Logging.pm
@@ -0,0 +1,134 @@
+# $NetBSD: Logging.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# Subroutines for printing messages to the user in a common format.
+# The subroutines all have the parameters C<$fname>,
+# C<$lineno> and C<$message>. In case there's no appropriate filename for
+# the message, NO_FILE may be passed, likewise for C<$lineno> and
+# NO_LINES. Before printing, the filename is normalized, that is,
+# "/foo/bar/../../" components are removed, as well as "." components.
+# At the end of the program, the subroutine print_summary_and_exit should
+# be called.
+#
+# Examples:
+# log_error(NO_FILE, NO_LINES, "Invalid command line.");
+# log_warning($fname, NO_LINES, "Not found.");
+# log_debug($fname, $lineno, sprintf("invalid character (0x%02x).", $c));
+#
+package PkgLint::Logging;
+
+use strict;
+use warnings;
+
+BEGIN {
+ use Exporter;
+ use vars qw(@ISA @EXPORT_OK);
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ NO_FILE NO_LINE_NUMBER NO_LINES
+ log_fatal log_error log_warning log_note log_debug
+ explain_error explain_warning explain_info
+ print_summary_and_exit
+ set_explain set_gcc_output_format
+ get_show_source_flag set_show_source_flag
+ );
+ import PkgLint::Util qw(
+ false true
+ normalize_pathname
+ );
+}
+
+use constant NO_FILE => undef;
+use constant NO_LINE_NUMBER => undef;
+use constant NO_LINES => undef;
+
+use enum qw(:LL_ FATAL ERROR WARNING NOTE DEBUG);
+
+use constant traditional_type => ["FATAL", "ERROR", "WARN", "NOTE", "DEBUG"];
+use constant gcc_type => ["fatal", "error", "warning", "note", "debug"];
+
+my $errors = 0;
+my $warnings = 0;
+my $gcc_output_format = false;
+my $explain_flag = false;
+my $show_source_flag = false;
+
+sub strxvis($) {
+ my ($s) = @_;
+
+ $s =~ s/([^\x09\x20-\x7e])/"\\x" . unpack("H*", $1)/eg;
+ return $s;
+}
+
+sub log_message { # no prototype due to Perl weirdness
+ my ($level, $fname, $lineno, $message) = @_;
+ my ($text, $sep);
+
+ if (defined($fname)) {
+ $fname = normalize_pathname($fname);
+ }
+
+ $text = "";
+ $sep = "";
+ if (!$gcc_output_format) {
+ $text .= "${sep}" . traditional_type->[$level] . ":";
+ $sep = " ";
+ }
+ if (defined($fname)) {
+ $text .= defined($lineno)
+ ? "${sep}${fname}:${lineno}"
+ : "${sep}${fname}";
+ $sep = ": ";
+ }
+ if ($gcc_output_format) {
+ $text .= "${sep}" . gcc_type->[$level] . ":";
+ $sep = " ";
+ }
+ if (defined($message)) {
+ $text .= $sep . strxvis($message);
+ $sep = "";
+ }
+
+ if ($level == LL_FATAL) {
+ print STDERR ("${text}\n");
+ } else {
+ print STDOUT ("${text}\n");
+ }
+}
+
+sub log_fatal($$$) { log_message(LL_FATAL, @_); exit(1); }
+sub log_error($$$) { log_message(LL_ERROR, @_); $errors++; }
+sub log_warning($$$) { log_message(LL_WARNING, @_); $warnings++; }
+sub log_note($$$) { log_message(LL_NOTE, @_); }
+sub log_debug($$$) { log_message(LL_DEBUG, @_); }
+
+sub explain { # no prototype due to Perl weirdness
+ my ($loglevel, $fname, $lines, @texts) = @_;
+ my $out = ($loglevel == LL_FATAL) ? *STDERR : *STDOUT;
+
+ if ($explain_flag) {
+ foreach my $text ("", @texts, "") {
+ print $out ("\t${text}\n");
+ }
+ }
+}
+sub explain_error($$@) { explain(LL_ERROR, @_); }
+sub explain_warning($$@) { explain(LL_WARNING, @_); }
+sub explain_note($$@) { explain(LL_NOTE, @_); }
+
+sub print_summary_and_exit($) {
+ my ($quiet) = @_;
+
+ if (!$quiet) {
+ if ($errors != 0 || $warnings != 0) {
+ print("$errors errors and $warnings warnings found." . ($explain_flag ? "" : " (Use -e for more details.)") . "\n");
+ } else {
+ print "looks fine.\n";
+ }
+ }
+ exit($errors != 0);
+}
+
+sub set_explain() { $explain_flag = true; }
+sub set_gcc_output_format() { $gcc_output_format = true; }
+sub get_show_source_flag() { return $show_source_flag; }
+sub set_show_source_flag() { $show_source_flag = true; }
diff --git a/pkgtools/pkglint4/files/PkgLint/Patches.pm b/pkgtools/pkglint4/files/PkgLint/Patches.pm
new file mode 100644
index 00000000000..b96e7daa5f6
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Patches.pm
@@ -0,0 +1,639 @@
+# $NetBSD: Patches.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# Everything concerning checks for patch files.
+#
+
+use strict;
+use warnings;
+
+# Guess the type of file based on the filename. This is used to select
+# the proper subroutine for detecting absolute pathnames.
+#
+# Returns one of "source", "shell", "make", "text", "configure",
+# "ignore", "unknown".
+#
+sub get_filetype($$) {
+ my ($line, $fname) = @_;
+ my $basename = basename($fname);
+
+ # The trailig .in part is not needed, since it does not
+ # influence the type of contents.
+ $basename =~ s,\.in$,,;
+
+ # Let's assume that everything else that looks like a Makefile
+ # is indeed a Makefile.
+ if ($basename =~ m"^I?[Mm]akefile(?:\..*|)?|.*\.ma?k$") {
+ return "make";
+ }
+
+ # Too many false positives for shell scripts, so configure
+ # scripts get their own category.
+ if ($basename =~ m"^configure(?:|\.ac)$") {
+ $opt_debug_unchecked and $line->log_debug("Skipped check for absolute pathnames.");
+ return "configure";
+ }
+
+ if ($basename =~ m"\.(?:sh|m4)$"i) {
+ return "shell";
+ }
+
+ if ($basename =~ m"\.(?:cc?|cpp|cxx|el|hh?|hpp|l|pl|pm|py|s|t|y)$"i) {
+ return "source";
+ }
+
+ if ($basename =~ m"^.+\.(?:\d+|conf|html|info|man|po|tex|texi|texinfo|txt|xml)$"i) {
+ return "text";
+ }
+
+ # Filenames without extension are hard to guess right. :(
+ if ($basename !~ m"\.") {
+ return "unknown";
+ }
+
+ $opt_debug_misc and $line->log_debug("Don't know the file type of ${fname}.");
+
+ return "unknown";
+}
+
+sub checkline_cpp_macro_names($$) {
+ my ($line, $text) = @_;
+ my ($rest);
+
+ use constant good_macros => PkgLint::Util::array_to_hash(qw(
+ __STDC__
+
+ __GNUC__ __GNUC_MINOR__
+ __SUNPRO_C
+
+ __i386
+ __mips
+ __sparc
+
+ __APPLE__
+ __bsdi__
+ __CYGWIN__
+ __DragonFly__
+ __FreeBSD__ __FreeBSD_version
+ __INTERIX
+ __linux__
+ __MINGW32__
+ __NetBSD__ __NetBSD_Version__
+ __OpenBSD__
+ __SVR4
+ __sgi
+ __sun
+
+ __GLIBC__
+ ));
+ use constant bad_macros => {
+ "__sgi__" => "__sgi",
+ "__sparc__" => "__sparc",
+ "__sparc_v9__" => "__sparcv9",
+ "__sun__" => "__sun",
+ "__svr4__" => "__SVR4",
+ };
+
+ $rest = $text;
+ while ($rest =~ s/defined\((__[\w_]+)\)// || $rest =~ s/\b(_\w+)\(//) {
+ my ($macro) = ($1);
+
+ if (exists(good_macros->{$macro})) {
+ $opt_debug_misc and $line->log_debug("Found good macro \"${macro}\".");
+ } elsif (exists(bad_macros->{$macro})) {
+ $line->log_warning("The macro \"${macro}\" is not portable enough. Please use \"".bad_macros->{$macro}."\" instead.");
+ $line->explain_warning("See the pkgsrc guide, section \"CPP defines\" for details.");
+
+ } elsif ($macro eq "__NetBSD_Prereq__") {
+ $line->log_warning("Please use __NetBSD_Version__ instead of __NetBSD_Prereq__.");
+ $line->explain_warning(
+"The __NetBSD_Prereq__ macro is pretty new. It was born in NetBSD",
+"4.99.3, and maybe it won't survive for long. A better (and compatible)",
+"way is to compare __NetBSD_Version__ directly to the required version",
+"number.");
+
+ } elsif ($macro =~ m"^_+NetBSD_+Version_+$"i && $macro ne "__NetBSD_Version__") {
+ $line->log_warning("Misspelled variant \"${macro}\" of \"__NetBSD_Version__\".");
+
+ } else {
+ $opt_debug_unchecked and $line->log_debug("Unchecked macro \"${macro}\".");
+ }
+ }
+}
+
+# Checks whether the line contains text that looks like absolute
+# pathnames, assuming that the file uses the common syntax with
+# single or double quotes to represent strings.
+#
+sub checkline_source_absolute_pathname($$) {
+ my ($line, $text) = @_;
+ my ($abspath);
+
+ $opt_debug_trace and $line->log_debug("checkline_source_absolute_pathname(${text})");
+
+ if ($text =~ m"(.*)([\"'])(/[^\"']*)\2") {
+ my ($before, $delim, $string) = ($1, $2, $3);
+
+ $opt_debug_misc and $line->log_debug("checkline_source_absolute_pathname(before=${before}, string=${string})");
+ if ($before =~ m"[A-Z_]+\s*$") {
+ # allowed: PREFIX "/bin/foo"
+
+ } elsif ($string =~ m"^/[*/]") {
+ # This is more likely to be a C or C++ comment.
+
+ } elsif ($string !~ m"^/\w") {
+ # Assume that pathnames start with a letter or digit.
+
+ } elsif ($before =~ m"\+\s*$") {
+ # Something like foodir + '/lib'
+
+ } else {
+ $abspath = $string;
+ }
+ }
+
+ if (defined($abspath)) {
+ checkword_absolute_pathname($line, $abspath);
+ }
+}
+
+# Last resort if the file does not look like a Makefile or typical
+# source code. All strings that look like pathnames and start with
+# one of the typical Unix prefixes are found.
+#
+sub checkline_other_absolute_pathname($$) {
+ my ($line, $text) = @_;
+
+ $opt_debug_trace and $line->log_debug("checkline_other_absolute_pathname(\"${text}\")");
+
+ if ($text =~ m"^#[^!]") {
+ # Don't warn for absolute pathnames in comments,
+ # except for shell interpreters.
+
+ } elsif ($text =~ m"^(.*?)((?:/[\w.]+)*/(?:bin|dev|etc|home|lib|mnt|opt|proc|sbin|tmp|usr|var)\b[\w./\-]*)(.*)$") {
+ my ($before, $path, $after) = ($1, $2, $3);
+
+ if ($before =~ m"\@$") {
+ # Something like @PREFIX@/bin
+
+ } elsif ($before =~ m"[)}]$") {
+ # Something like ${prefix}/bin or $(PREFIX)/bin
+
+ } elsif ($before =~ m"\+\s*[\"']$") {
+ # Something like foodir + '/lib'
+
+ } elsif ($before =~ m"\w$") {
+ # Something like $dir/lib
+
+ } elsif ($before =~ m"\.$") {
+ # ../foo is not an absolute pathname.
+
+ } else {
+ $opt_debug_misc and $line->log_debug("before=${before}");
+ checkword_absolute_pathname($line, $path);
+ }
+ }
+}
+
+sub checkfile_patch($) {
+ my ($fname) = @_;
+ my ($lines);
+ my ($state, $redostate, $nextstate, $dellines, $addlines, $hunks);
+ my ($seen_comment, $current_fname, $current_ftype, $patched_files);
+ my ($leading_context_lines, $trailing_context_lines, $context_scanning_leading);
+
+ # Abbreviations used:
+ # style: [c] = context diff, [u] = unified diff
+ # scope: [f] = file, [h] = hunk, [l] = line
+ # action: [d] = delete, [m] = modify, [a] = add, [c] = context
+ use constant re_patch_rcsid => qr"^\$.*\$$";
+ use constant re_patch_text => qr"^(.+)$";
+ use constant re_patch_empty => qr"^$";
+ use constant re_patch_cfd => qr"^\*\*\*\s(\S+)(.*)$";
+ use constant re_patch_cfa => qr"^---\s(\S+)(.*)$";
+ use constant re_patch_ch => qr"^\*{15}(.*)$";
+ use constant re_patch_chd => qr"^\*{3}\s(\d+)(?:,(\d+))?\s\*{4}$";
+ use constant re_patch_cha => qr"^-{3}\s(\d+)(?:,(\d+))?\s-{4}$";
+ use constant re_patch_cld => qr"^(?:-\s(.*))?$";
+ use constant re_patch_clm => qr"^(?:!\s(.*))?$";
+ use constant re_patch_cla => qr"^(?:\+\s(.*))?$";
+ use constant re_patch_clc => qr"^(?:\s\s(.*))?$";
+ use constant re_patch_ufd => qr"^---\s(\S+)(?:\s+(.*))?$";
+ use constant re_patch_ufa => qr"^\+{3}\s(\S+)(?:\s+(.*))?$";
+ use constant re_patch_uh => qr"^\@\@\s-(?:(\d+),)?(\d+)\s\+(?:(\d+),)?(\d+)\s\@\@(.*)$";
+ use constant re_patch_uld => qr"^-(.*)$";
+ use constant re_patch_ula => qr"^\+(.*)$";
+ use constant re_patch_ulc => qr"^\s(.*)$";
+ use constant re_patch_ulnonl => qr"^\\ No newline at end of file$";
+
+ use enum qw(:PST_
+ START CENTER TEXT
+ CFA CH CHD CLD0 CLD CLA0 CLA
+ UFA UH UL
+ );
+
+ my @comment_explanation = (
+"Each patch must document why it is necessary. If it has been applied",
+"because of a security issue, a reference to the CVE should be mentioned",
+"as well.",
+"",
+"Since it is our goal to have as few patches as possible, all patches",
+"should be sent to the upstream maintainers of the package. After you",
+"have done so, you should add a reference to the bug report containing",
+"the patch.");
+
+ my ($line, $m);
+
+ my $check_text = sub($) {
+ my ($text) = @_;
+
+ if ($text =~ m"(\$(Author|Date|Header|Id|Locker|Log|Name|RCSfile|Revision|Source|State|$opt_rcsidstring)(?::[^\$]*)?\$)") {
+ my ($tag) = ($2);
+
+ if ($text =~ re_patch_uh) {
+ $line->log_warning("Found RCS tag \"\$${tag}\$\". Please remove it.");
+ $line->set_text($1);
+ } else {
+ $line->log_warning("Found RCS tag \"\$${tag}\$\". Please remove it by reducing the number of context lines using pkgdiff or \"diff -U[210]\".");
+ }
+ }
+ };
+
+ my $check_contents = sub() {
+
+ if ($m->has(1)) {
+ $check_text->($m->text(1));
+ }
+ };
+
+ my $check_added_contents = sub() {
+ my $text;
+
+ return unless $m->has(1);
+ $text = $m->text(1);
+ checkline_cpp_macro_names($line, $text);
+
+ # XXX: This check is not as accurate as the similar one in
+ # checkline_mk_shelltext().
+ if (defined($current_fname)) {
+ if ($current_ftype eq "shell" || $current_ftype eq "make") {
+ my ($mm, $rest) = match_all($text, $regex_shellword);
+
+ foreach my $m (@{$mm}) {
+ my $shellword = $m->text(1);
+
+ if ($shellword =~ m"^#") {
+ last;
+ }
+ checkline_mk_absolute_pathname($line, $shellword);
+ }
+
+ } elsif ($current_ftype eq "source") {
+ checkline_source_absolute_pathname($line, $text);
+
+ } elsif ($current_ftype eq "configure") {
+ if ($text =~ m": Avoid regenerating within pkgsrc$") {
+ $line->log_error("This code must not be included in patches.");
+ $line->explain_error(
+"It is generated automatically by pkgsrc after the patch phase.",
+"",
+"For more details, look for \"configure-scripts-override\" in",
+"mk/configure/gnu-configure.mk.");
+ }
+
+ } elsif ($current_ftype eq "ignore") {
+ # Ignore it.
+
+ } else {
+ checkline_other_absolute_pathname($line, $text);
+ }
+ }
+ };
+
+ my $check_hunk_end = sub($$$) {
+ my ($deldelta, $adddelta, $newstate) = @_;
+
+ if ($deldelta > 0 && $dellines == 0) {
+ $redostate = $newstate;
+ if (defined($addlines) && $addlines > 0) {
+ $line->log_error("Expected ${addlines} more lines to be added.");
+ }
+ } elsif ($adddelta > 0 && $addlines == 0) {
+ $redostate = $newstate;
+ if (defined($dellines) && $dellines > 0) {
+ $line->log_error("Expected ${dellines} more lines to be deleted.");
+ }
+ } else {
+ if (defined($context_scanning_leading)) {
+ if ($deldelta != 0 && $adddelta != 0) {
+ if ($context_scanning_leading) {
+ $leading_context_lines++;
+ } else {
+ $trailing_context_lines++;
+ }
+ } else {
+ if ($context_scanning_leading) {
+ $context_scanning_leading = false;
+ } else {
+ $trailing_context_lines = 0;
+ }
+ }
+ }
+
+ if ($deldelta != 0) {
+ $dellines -= $deldelta;
+ }
+ if ($adddelta != 0) {
+ $addlines -= $adddelta;
+ }
+ if (!((defined($dellines) && $dellines > 0) ||
+ (defined($addlines) && $addlines > 0))) {
+ if (defined($context_scanning_leading)) {
+ if ($leading_context_lines != $trailing_context_lines) {
+ $opt_debug_patches and $line->log_warning("The hunk that ends here does not have as many leading (${leading_context_lines}) as trailing (${trailing_context_lines}) lines of context.");
+ }
+ }
+ $nextstate = $newstate;
+ }
+ }
+ };
+
+ # @param deldelta
+ # The number of lines that are deleted from the patched file.
+ # @param adddelta
+ # The number of lines that are added to the patched file.
+ # @param newstate
+ # The follow-up state when this line is the last line to be
+ # added in this hunk of the patch.
+ #
+ my $check_hunk_line = sub($$$) {
+ my ($deldelta, $adddelta, $newstate) = @_;
+
+ $check_contents->();
+ $check_hunk_end->($deldelta, $adddelta, $newstate);
+
+ # If -Wextra is given, the context lines are checked for
+ # absolute paths and similar things. If it is not given,
+ # only those lines that really add something to the patched
+ # file are checked.
+ if ($adddelta != 0 && ($deldelta == 0 || $opt_warn_extra)) {
+ $check_added_contents->();
+ }
+ };
+
+ # [ regex, to state, action ]
+ my $transitions = {
+ PST_START() =>
+ [ [re_patch_rcsid, PST_CENTER, sub() {
+ checkline_rcsid($line, "");
+ }], [undef, PST_CENTER, sub() {
+ checkline_rcsid($line, "");
+ }]],
+ PST_CENTER() =>
+ [ [re_patch_empty, PST_TEXT, sub() {
+ #
+ }], [re_patch_cfd, PST_CFA, sub() {
+ if ($seen_comment) {
+ $opt_warn_space and $line->log_note("Empty line expected.");
+ } else {
+ $line->log_error("Comment expected.");
+ $line->explain_error(@comment_explanation);
+ }
+ $line->log_warning("Please use unified diffs (diff -u) for patches.");
+ }], [re_patch_ufd, PST_UFA, sub() {
+ if ($seen_comment) {
+ $opt_warn_space and $line->log_note("Empty line expected.");
+ } else {
+ $line->log_error("Comment expected.");
+ $line->explain_error(@comment_explanation);
+ }
+ }], [undef, PST_TEXT, sub() {
+ $opt_warn_space and $line->log_note("Empty line expected.");
+ }]],
+ PST_TEXT() =>
+ [ [re_patch_cfd, PST_CFA, sub() {
+ if (!$seen_comment) {
+ $line->log_error("Comment expected.");
+ $line->explain_error(@comment_explanation);
+ }
+ $line->log_warning("Please use unified diffs (diff -u) for patches.");
+ }], [re_patch_ufd, PST_UFA, sub() {
+ if (!$seen_comment) {
+ $line->log_error("Comment expected.");
+ $line->explain_error(@comment_explanation);
+ }
+ }], [re_patch_text, PST_TEXT, sub() {
+ $seen_comment = true;
+ }], [re_patch_empty, PST_TEXT, sub() {
+ #
+ }], [undef, PST_TEXT, sub() {
+ #
+ }]],
+ PST_CFA() =>
+ [ [re_patch_cfa, PST_CH, sub() {
+ $current_fname = $m->text(1);
+ $current_ftype = get_filetype($line, $current_fname);
+ $opt_debug_patches and $line->log_debug("fname=$current_fname ftype=$current_ftype");
+ $patched_files++;
+ $hunks = 0;
+ }]],
+ PST_CH() =>
+ [ [re_patch_ch, PST_CHD, sub() {
+ $hunks++;
+ }]],
+ PST_CHD() =>
+ [ [re_patch_chd, PST_CLD0, sub() {
+ $dellines = ($m->has(2))
+ ? (1 + $m->text(2) - $m->text(1))
+ : ($m->text(1));
+ }]],
+ PST_CLD0() =>
+ [ [re_patch_clc, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [re_patch_cld, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [re_patch_clm, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [re_patch_cha, PST_CLA0, sub() {
+ $dellines = undef;
+ $addlines = ($m->has(2))
+ ? (1 + $m->text(2) - $m->text(1))
+ : ($m->text(1));
+ }]],
+ PST_CLD() =>
+ [ [re_patch_clc, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [re_patch_cld, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [re_patch_clm, PST_CLD, sub() {
+ $check_hunk_line->(1, 0, PST_CLD0);
+ }], [undef, PST_CLD0, sub() {
+ if ($dellines != 0) {
+ $line->log_warning("Invalid number of deleted lines (${dellines} missing).");
+ }
+ }]],
+ PST_CLA0() =>
+ [ [re_patch_clc, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [re_patch_clm, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [re_patch_cla, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [undef, PST_CH, sub() {
+ #
+ }]],
+ PST_CLA() =>
+ [ [re_patch_clc, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [re_patch_clm, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [re_patch_cla, PST_CLA, sub() {
+ $check_hunk_line->(0, 1, PST_CH);
+ }], [undef, PST_CLA0, sub() {
+ if ($addlines != 0) {
+ $line->log_warning("Invalid number of added lines (${addlines} missing).");
+ }
+ }]],
+ PST_CH() =>
+ [ [undef, PST_TEXT, sub() {
+ #
+ }]],
+ PST_UFA() =>
+ [ [re_patch_ufa, PST_UH, sub() {
+ $current_fname = $m->text(1);
+ $current_ftype = get_filetype($line, $current_fname);
+ $opt_debug_patches and $line->log_debug("fname=$current_fname ftype=$current_ftype");
+ $patched_files++;
+ $hunks = 0;
+ }]],
+ PST_UH() =>
+ [ [re_patch_uh, PST_UL, sub() {
+ $dellines = ($m->has(1) ? $m->text(2) : 1);
+ $addlines = ($m->has(3) ? $m->text(4) : 1);
+ $check_text->($line->text);
+ if ($line->text =~ m"\r$") {
+ $line->log_error("The hunk header must not end with a CR character.");
+ $line->explain_error(
+"The MacOS X patch utility cannot handle these.");
+ }
+ $hunks++;
+ $context_scanning_leading = (($m->has(1) && $m->text(1) ne "1") ? true : undef);
+ $leading_context_lines = 0;
+ $trailing_context_lines = 0;
+ }], [undef, PST_TEXT, sub() {
+ ($hunks != 0) || $line->log_warning("No hunks for file ${current_fname}.");
+ }]],
+ PST_UL() =>
+ [ [re_patch_uld, PST_UL, sub() {
+ $check_hunk_line->(1, 0, PST_UH);
+ }], [re_patch_ula, PST_UL, sub() {
+ $check_hunk_line->(0, 1, PST_UH);
+ }], [re_patch_ulc, PST_UL, sub() {
+ $check_hunk_line->(1, 1, PST_UH);
+ }], [re_patch_ulnonl, PST_UL, sub() {
+ #
+ }], [re_patch_empty, PST_UL, sub() {
+ $opt_warn_space and $line->log_note("Leading white-space missing in hunk.");
+ $check_hunk_line->(1, 1, PST_UH);
+ }], [undef, PST_UH, sub() {
+ if ($dellines != 0 || $addlines != 0) {
+ $line->log_warning("Unexpected end of hunk (-${dellines},+${addlines} expected).");
+ }
+ }]]};
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_patch()");
+
+ checkperms($fname);
+ if (!($lines = load_lines($fname, false))) {
+ 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;
+ }
+
+ $state = PST_START;
+ $dellines = undef;
+ $addlines = undef;
+ $patched_files = 0;
+ $seen_comment = false;
+ $current_fname = undef;
+ $current_ftype = undef;
+ $hunks = undef;
+
+ for (my $lineno = 0; $lineno <= $#{$lines}; ) {
+ $line = $lines->[$lineno];
+ my $text = $line->text;
+
+ $opt_debug_patches and $line->log_debug("[${state} ${patched_files}/".($hunks||0)."/-".($dellines||0)."+".($addlines||0)."] $text");
+
+ my $found = false;
+ foreach my $t (@{$transitions->{$state}}) {
+ if (!defined($t->[0])) {
+ $m = undef;
+ } elsif ($text =~ $t->[0]) {
+ $opt_debug_patches and $line->log_debug($t->[0]);
+ $m = PkgLint::SimpleMatch->new($text, \@-, \@+);
+ } else {
+ next;
+ }
+ $redostate = undef;
+ $nextstate = $t->[1];
+ $t->[2]->();
+ if (defined($redostate)) {
+ $state = $redostate;
+ } else {
+ $state = $nextstate;
+ if (defined($t->[0])) {
+ $lineno++;
+ }
+ }
+ $found = true;
+ last;
+ }
+
+ if (!$found) {
+ $line->log_error("Parse error: state=${state}");
+ $state = PST_TEXT;
+ $lineno++;
+ }
+ }
+
+ while ($state != PST_TEXT) {
+ $opt_debug_patches and log_debug($fname, "EOF", "[${state} ${patched_files}/".($hunks||0)."/-".($dellines||0)."+".($addlines||0)."]");
+
+ my $found = false;
+ foreach my $t (@{$transitions->{$state}}) {
+ if (!defined($t->[0])) {
+ my $newstate;
+
+ $m = undef;
+ $redostate = undef;
+ $nextstate = $t->[1];
+ $t->[2]->();
+ $newstate = (defined($redostate)) ? $redostate : $nextstate;
+ if ($newstate == $state) {
+ log_fatal($fname, "EOF", "Internal error in the patch transition table.");
+ }
+ $state = $newstate;
+ $found = true;
+ last;
+ }
+ }
+
+ if (!$found) {
+ log_error($fname, "EOF", "Parse error: state=${state}");
+ $state = PST_TEXT;
+ }
+ }
+
+ if ($patched_files > 1) {
+ log_warning($fname, NO_LINE_NUMBER, "Contains patches for $patched_files files, should be only one.");
+
+ } elsif ($patched_files == 0) {
+ log_error($fname, NO_LINE_NUMBER, "Contains no patch.");
+ }
+
+ checklines_trailing_empty_lines($lines);
+}
+
diff --git a/pkgtools/pkglint4/files/PkgLint/Shell.pm b/pkgtools/pkglint4/files/PkgLint/Shell.pm
new file mode 100644
index 00000000000..c9251b9ba04
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Shell.pm
@@ -0,0 +1,708 @@
+# $NetBSD: Shell.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# Parsing and checking shell commands embedded in Makefiles
+#
+
+sub checkline_mk_shellword($$$) {
+ my ($line, $shellword, $check_quoting) = @_;
+ my ($rest, $state, $value);
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_shellword(\"${shellword}\", ${check_quoting}).");
+ use constant shellcommand_context_type => PkgLint::Type->new(
+ LK_NONE, "ShellCommand", [[ qr".*", "adsu" ]], NOT_GUESSED
+ );
+ use constant shellword_vuc => PkgLint::VarUseContext->new(
+ VUC_TIME_UNKNOWN,
+ shellcommand_context_type,
+ VUC_SHELLWORD_PLAIN,
+ VUC_EXTENT_WORD
+ );
+
+ if ($shellword =~ m"^\$\{(${regex_varname})(:[^{}]+)?\}$") {
+ my ($varname, $mod) = ($1, $2);
+
+ checkline_mk_varuse($line, $varname, defined($mod) ? $mod : "", shellword_vuc);
+ return;
+ }
+
+ if ($shellword =~ m"\$\{PREFIX\}/man(?:$|/)") {
+ $line->log_warning("Please use \${PKGMANDIR} instead of \"man\".");
+ }
+
+ if ($shellword =~ m"etc/rc\.d") {
+ $line->log_warning("Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to \${RCD_SCRIPTS_EXAMPLEDIR}.");
+ }
+
+ # Note: SWST means [S]hell[W]ord [ST]ate
+ use enum qw(:SWST_ PLAIN SQUOT DQUOT DQUOT_BACKT BACKT);
+ use constant statename => [
+ "SWST_PLAIN", "SWST_SQUOT", "SWST_DQUOT",
+ "SWST_DQUOT_BACKT", "SWST_BACKT",
+ ];
+ use constant user_statename => [
+ "unquoted string", "single quoted string",
+ "double quoted string", "backticks inside double quoted string",
+ "backticks",
+ ];
+
+ $rest = ($shellword =~ m"^#") ? "" : $shellword;
+ $state = SWST_PLAIN;
+ while ($rest ne "") {
+
+ $opt_debug_shell and $line->log_debug(statename->[$state] . ": ${rest}");
+
+ # When we are parsing inside backticks, it is more
+ # reasonable to check the whole shell command
+ # recursively, instead of splitting off the first
+ # make(1) variable (see the elsif below).
+ if ($state == SWST_BACKT || $state == SWST_DQUOT_BACKT) {
+
+ # Scan for the end of the backticks, checking
+ # for single backslashes and removing one level
+ # of backslashes. Backslashes are only removed
+ # before a dollar, a backslash or a backtick.
+ #
+ # References:
+ # * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03
+ my $stripped = "";
+ while ($rest ne "") {
+ if ($rest =~ s/^\`//) { #`
+ $state = ($state == SWST_BACKT) ? SWST_PLAIN : SWST_DQUOT;
+ goto end_of_backquotes;
+ } elsif ($rest =~ s/^\\([\\\`\$])//) { #`
+ $stripped .= $1;
+ } elsif ($rest =~ s/^(\\)//) {
+ $line->log_warning("Backslashes should be doubled inside backticks.");
+ $stripped .= $1;
+ } elsif ($state == SWST_DQUOT_BACKT && $rest =~ s/^"//) { #"
+ $line->log_warning("Double quotes inside backticks inside double quotes are error prone.");
+ $line->explain_warning(
+"According to the SUSv3, they produce undefined results.",
+"",
+"See the paragraph starting \"Within the backquoted ...\" in",
+"http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html");
+ } elsif ($rest =~ s/^([^\\\`]+)//) { #`
+ $stripped .= $1;
+ } else {
+ assert(false, "rest=$rest");
+ }
+ }
+ $line->log_error("Unfinished backquotes: rest=$rest");
+
+ end_of_backquotes:
+ # Check the resulting command.
+ checkline_mk_shelltext($line, $stripped);
+
+ # make(1) variables have the same syntax, no matter in
+ # which state we are currently.
+ } elsif ($rest =~ s/^\$\{(${regex_varname}|[\@])(:[^\{]+)?\}//
+ || $rest =~ s/^\$\((${regex_varname}|[\@])(:[^\)]+)?\)//
+ || $rest =~ s/^\$([\w\@])//) {
+ my ($varname, $mod) = ($1, $2);
+
+ if ($varname eq "\@") {
+ $line->log_warning("Please use \"\${.TARGET}\" instead of \"\$\@\".");
+ $line->explain_warning(
+"The variable \$\@ can easily be confused with the shell variable of the",
+"same name, which has a completely different meaning.");
+ $varname = ".TARGET";
+ }
+
+ if ($state == SWST_PLAIN && defined($mod) && $mod =~ m":Q$") {
+ # Fine.
+
+ } elsif ($state == SWST_BACKT) {
+ # Don't check here, to avoid false positives
+ # for tool names.
+
+ } elsif (($state == SWST_SQUOT || $state == SWST_DQUOT) && $varname =~ m"^(?:.*DIR|.*FILE|.*PATH|.*_VAR|PREFIX|.*BASE|PKGNAME)$") {
+ # This is ok if we don't allow these
+ # variables to have embedded [\$\\\"\'\`].
+
+ } elsif ($state == SWST_DQUOT && defined($mod) && $mod =~ m":Q$") {
+ $line->log_warning("Please don't use the :Q operator in double quotes.");
+ $line->explain_warning(
+"Either remove the :Q or the double quotes. In most cases, it is more",
+"appropriate to remove the double quotes.");
+
+ }
+
+ my $ctx = PkgLint::VarUseContext->new_from_pool(
+ VUC_TIME_UNKNOWN,
+ shellcommand_context_type,
+ ($state == SWST_PLAIN) ? VUC_SHELLWORD_PLAIN
+ : ($state == SWST_DQUOT) ? VUC_SHELLWORD_DQUOT
+ : ($state == SWST_SQUOT) ? VUC_SHELLWORD_SQUOT
+ : ($state == SWST_BACKT) ? VUC_SHELLWORD_BACKT
+ : VUC_SHELLWORD_UNKNOWN,
+ VUC_EXTENT_WORD_PART
+ );
+ if ($varname ne "\@") {
+ checkline_mk_varuse($line, $varname, defined($mod) ? $mod : "", $ctx);
+ }
+
+ # The syntax of the variable modifiers can get quite
+ # hairy. In lack of motivation, we just skip anything
+ # complicated, hoping that at least the braces are
+ # balanced.
+ } elsif ($rest =~ s/^\$\{//) {
+ my $braces = 1;
+ while ($rest ne "" && $braces > 0) {
+ if ($rest =~ s/^\}//) {
+ $braces--;
+ } elsif ($rest =~ s/^\{//) {
+ $braces++;
+ } elsif ($rest =~ s/^[^{}]+//) {
+ # Nothing to do here.
+ } else {
+ last;
+ }
+ }
+
+ } elsif ($state == SWST_PLAIN) {
+
+ # XXX: This is only true for the "first" word in a
+ # shell command, not for every word. For example,
+ # FOO_ENV+= VAR=`command` may be underquoted.
+ if (false && $rest =~ m"([\w_]+)=\"\`") {
+ $line->log_note("In the assignment to \"$1\", you don't need double quotes around backticks.");
+ $line->explain_note(
+"Assignments are a special context, where no double quotes are needed",
+"around backticks. In other contexts, the double quotes are necessary.");
+ }
+
+ if ($rest =~ s/^[!#\%&\(\)*+,\-.\/0-9:;<=>?\@A-Z\[\]^_a-z{|}~]+//) {
+ } elsif ($rest =~ s/^\'//) {
+ $state = SWST_SQUOT;
+ } elsif ($rest =~ s/^\"//) {
+ $state = SWST_DQUOT;
+ } elsif ($rest =~ s/^\`//) { #`
+ $state = SWST_BACKT;
+ } elsif ($rest =~ s/^\\(?:[ !"#'\(\)*;?\\^{|}]|\$\$)//) {
+ } elsif ($rest =~ s/^\$\$([0-9A-Z_a-z]+|\#)//
+ || $rest =~ s/^\$\$\{([0-9A-Z_a-z]+|\#)\}//
+ || $rest =~ s/^\$\$(\$)\$//) {
+ my ($shvarname) = ($1);
+ if ($opt_warn_quoting && $check_quoting) {
+ $line->log_warning("Unquoted shell variable \"${shvarname}\".");
+ $line->explain_warning(
+"When a shell variable contains white-space, it is expanded (split into",
+"multiple words) when it is written as \$variable in a shell script.",
+"If that is not intended, you should add quotation marks around it,",
+"like \"\$variable\". Then, the variable will always expand to a single",
+"word, preserving all white-space and other special characters.",
+"",
+"Example:",
+"\tfname=\"Curriculum vitae.doc\"",
+"\tcp \$fname /tmp",
+"\t# tries to copy the two files \"Curriculum\" and \"Vitae.doc\"",
+"\tcp \"\$fname\" /tmp",
+"\t# copies one file, as intended");
+ }
+
+ } elsif ($rest =~ s/^\$\@//) {
+ $line->log_warning("Please use \"\${.TARGET}\" instead of \"\$@\".");
+ $line->explain_warning(
+"It is more readable and prevents confusion with the shell variable of",
+"the same name.");
+
+ } elsif ($rest =~ s/^\$\$\@//) {
+ $line->log_warning("The \$@ shell variable should only be used in double quotes.");
+
+ } elsif ($rest =~ s/^\$\$\?//) {
+ $line->log_warning("The \$? shell variable is often not available in \"set -e\" mode.");
+
+ } elsif ($rest =~ s/^\$\$\(/(/) {
+ $line->log_warning("Invoking subshells via \$(...) is not portable enough.");
+ $line->explain_warning(
+"The Solaris /bin/sh does not know this way to execute a command in a",
+"subshell. Please use backticks (\`...\`) as a replacement.");
+
+ } else {
+ last;
+ }
+
+ } elsif ($state == SWST_SQUOT) {
+ if ($rest =~ s/^\'//) {
+ $state = SWST_PLAIN;
+ } elsif ($rest =~ s/^[^\$\']+//) { #'
+ } elsif ($rest =~ s/^\$\$//) {
+ } else {
+ last;
+ }
+
+ } elsif ($state == SWST_DQUOT) {
+ if ($rest =~ s/^\"//) {
+ $state = SWST_PLAIN;
+ } elsif ($rest =~ s/^\`//) { #`
+ $state = SWST_DQUOT_BACKT;
+ } elsif ($rest =~ s/^[^\$"\\\`]+//) { #`
+ } elsif ($rest =~ s/^\\(?:[\\\"\`]|\$\$)//) { #`
+ } elsif ($rest =~ s/^\$\$\{([0-9A-Za-z_]+)\}//
+ || $rest =~ s/^\$\$([0-9A-Z_a-z]+|[!#?\@]|\$\$)//) {
+ my ($shvarname) = ($1);
+ $opt_debug_shell and $line->log_debug("[checkline_mk_shellword] Found double-quoted variable ${shvarname}.");
+ } elsif ($rest =~ s/^\$\$//) {
+ $line->log_warning("Unquoted \$ or strange shell variable found.");
+ } elsif ($rest =~ s/^\\(.)//) {
+ my ($char) = ($1);
+ $line->log_warning("Please use \"\\\\${char}\" instead of \"\\${char}\".");
+ $line->explain_warning(
+"Although the current code may work, it is not good style to rely on",
+"the shell passing \"\\${char}\" exactly as is, and not discarding the",
+"backslash. Alternatively you can use single quotes instead of double",
+"quotes.");
+ } else {
+ last;
+ }
+
+ } else {
+ last;
+ }
+ }
+ if ($rest !~ m"^\s*$") {
+ $line->log_error("Internal pkglint error: " . statename->[$state] . ": rest=${rest}");
+ }
+}
+
+# Some shell commands should not be used in the install phase.
+#
+sub checkline_mk_shellcmd_use($$) {
+ my ($line, $shellcmd) = @_;
+
+ use constant allowed_install_commands => array_to_hash(qw(
+ ${INSTALL}
+ ${INSTALL_DATA} ${INSTALL_DATA_DIR}
+ ${INSTALL_LIB} ${INSTALL_LIB_DIR}
+ ${INSTALL_MAN} ${INSTALL_MAN_DIR}
+ ${INSTALL_PROGRAM} ${INSTALL_PROGRAM_DIR}
+ ${INSTALL_SCRIPT}
+ ${LIBTOOL}
+ ${LN}
+ ${PAX}
+ ));
+ use constant discouraged_install_commands => array_to_hash(qw(
+ sed ${SED}
+ tr ${TR}
+ ));
+
+ if (defined($mkctx_target) && $mkctx_target =~ m"^(?:pre|do|post)-install") {
+
+ if (exists(allowed_install_commands->{$shellcmd})) {
+ # Fine.
+
+ } elsif (exists(discouraged_install_commands->{$shellcmd})) {
+ $line->log_warning("The shell command \"${shellcmd}\" should not be used in the install phase.");
+ $line->explain_warning(
+"In the install phase, the only thing that should be done is to install",
+"the prepared files to their final location. The file's contents should",
+"not be changed anymore.");
+
+ } elsif ($shellcmd eq "\${CP}") {
+ $line->log_warning("\${CP} should not be used to install files.");
+ $line->explain_warning(
+"The \${CP} command is highly platform dependent and cannot overwrite",
+"files that don't have write permission. Please use \${PAX} instead.",
+"",
+"For example, instead of",
+"\t\${CP} -R \${WRKSRC}/* \${PREFIX}/foodir",
+"you should use",
+"\tcd \${WRKSRC} && \${PAX} -wr * \${PREFIX}/foodir");
+
+ } else {
+ $opt_debug_misc and $line->log_debug("May \"${shellcmd}\" be used in the install phase?");
+ }
+ }
+}
+
+sub checkline_mk_shelltext($$) {
+ my ($line, $text) = @_;
+ my ($vartools, $state, $rest, $set_e_mode);
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_shelltext(\"$text\")");
+
+ # Note: SCST is the abbreviation for [S]hell [C]ommand [ST]ate.
+ use constant scst => qw(
+ START CONT
+ INSTALL INSTALL_D
+ MKDIR
+ PAX PAX_S
+ SED SED_E
+ SET SET_CONT
+ COND COND_CONT
+ CASE CASE_IN CASE_LABEL CASE_LABEL_CONT CASE_PAREN
+ FOR FOR_IN FOR_CONT
+ ECHO
+ INSTALL_DIR INSTALL_DIR2
+ );
+ use enum (":SCST_", scst);
+ use constant scst_statename => [ map { "SCST_$_" } scst ];
+
+ use constant forbidden_commands => array_to_hash(qw(
+ ktrace
+ mktexlsr
+ strace
+ texconfig truss
+ ));
+
+ if ($text =~ m"\$\{SED\}" && $text =~ m"\$\{MV\}") {
+ $line->log_note("Please use the SUBST framework instead of \${SED} and \${MV}.");
+ $line->explain_note(
+"When converting things, pay attention to \"#\" characters. In shell",
+"commands make(1) does not interpret them as comment character, but",
+"in other lines it does. Therefore, instead of the shell command",
+"",
+"\tsed -e 's,#define foo,,'",
+"",
+"you need to write",
+"",
+"\tSUBST_SED.foo+=\t's,\\#define foo,,'");
+ }
+
+ if ($text =~ m"^\@*-(.*(MKDIR|INSTALL.*-d|INSTALL_.*_DIR).*)") {
+ my ($mkdir_cmd) = ($1);
+
+ $line->log_note("You don't need to use \"-\" before ${mkdir_cmd}.");
+ }
+
+ $vartools = get_vartool_names();
+ $rest = $text;
+
+ use constant hidden_shell_commands => array_to_hash(qw(
+ ${DELAYED_ERROR_MSG} ${DELAYED_WARNING_MSG}
+ ${DO_NADA}
+ ${ECHO} ${ECHO_MSG} ${ECHO_N} ${ERROR_CAT} ${ERROR_MSG}
+ ${FAIL_MSG}
+ ${PHASE_MSG} ${PRINTF}
+ ${SHCOMMENT} ${STEP_MSG}
+ ${WARNING_CAT} ${WARNING_MSG}
+ ));
+
+ $set_e_mode = false;
+
+ if ($rest =~ s/^\s*([-@]*)(\$\{_PKG_SILENT\}\$\{_PKG_DEBUG\}|\$\{RUN\}|)//) {
+ my ($hidden, $macro) = ($1, $2);
+
+ if ($hidden !~ m"\@") {
+ # Nothing is hidden at all.
+
+ } elsif (defined($mkctx_target) && $mkctx_target =~ m"^(?:show-.*|.*-message)$") {
+ # In some targets commands may be hidden.
+
+ } elsif ($rest =~ m"^#") {
+ # Shell comments may be hidden, as they have no side effects
+
+ } elsif ($rest =~ $regex_shellword) {
+ my ($cmd) = ($1);
+
+ if (!exists(hidden_shell_commands->{$cmd})) {
+ $line->log_warning("The shell command \"${cmd}\" should not be hidden.");
+ $line->explain_warning(
+"Hidden shell commands do not appear on the terminal or in the log file",
+"when they are executed. When they fail, the error message cannot be",
+"assigned to the command, which is very difficult to debug.");
+ }
+ }
+
+ if ($hidden =~ m"-") {
+ $line->log_warning("The use of a leading \"-\" to suppress errors is deprecated.");
+ $line->explain_warning(
+"If you really want to ignore any errors from this command (including",
+"all errors you never thought of), append \"|| \${TRUE}\" to the",
+"command.");
+ }
+
+ if ($macro eq "\${RUN}") {
+ $set_e_mode = true;
+ }
+ }
+
+ $state = SCST_START;
+ while ($rest =~ s/^$regex_shellword//) {
+ my ($shellword) = ($1);
+
+ $opt_debug_shell and $line->log_debug(scst_statename->[$state] . ": ${shellword}");
+
+ checkline_mk_shellword($line, $shellword, !(
+ $state == SCST_CASE
+ || $state == SCST_FOR_CONT
+ || $state == SCST_SET_CONT
+ || ($state == SCST_START && $shellword =~ regex_sh_varassign)));
+
+ #
+ # Actions associated with the current state
+ # and the symbol on the "input tape".
+ #
+
+ if ($state == SCST_START || $state == SCST_COND) {
+ my ($type);
+
+ if ($shellword eq "\${RUN}") {
+ # Just skip this one.
+
+ } elsif (exists(forbidden_commands->{basename($shellword)})) {
+ $line->log_error("${shellword} must not be used in Makefiles.");
+ $line->explain_error(
+"This command must appear in INSTALL scripts, not in the package",
+"Makefile, so that the package also works if it is installed as a binary",
+"package via pkg_add.");
+
+ } elsif (exists(get_tool_names()->{$shellword})) {
+ if (!exists($mkctx_tools->{$shellword}) && !exists($mkctx_tools->{"g$shellword"})) {
+ $line->log_warning("The \"${shellword}\" tool is used but not added to USE_TOOLS.");
+ }
+
+ if (exists(get_required_vartools()->{$shellword})) {
+ $line->log_warning("Please use \"\${" . get_vartool_names()->{$shellword} . "}\" instead of \"${shellword}\".");
+ }
+
+ checkline_mk_shellcmd_use($line, $shellword);
+
+ } elsif ($shellword =~ m"^\$\{([\w_]+)\}$" && exists(get_varname_to_toolname()->{$1})) {
+ my ($vartool) = ($1);
+ my $plain_tool = get_varname_to_toolname()->{$vartool};
+
+ if (!exists($mkctx_tools->{$plain_tool})) {
+ $line->log_warning("The \"${plain_tool}\" tool is used but not added to USE_TOOLS.");
+ }
+
+ # Deactivated to allow package developers to write
+ # consistent code that uses ${TOOL} in all places.
+ if (false && defined($mkctx_target) && $mkctx_target =~ m"^(?:pre|do|post)-(?:extract|patch|wrapper|configure|build|install|package|clean)$") {
+ if (!exists(get_required_vartool_varnames()->{$vartool})) {
+ $opt_warn_extra and $line->log_note("You can write \"${plain_tool}\" instead of \"${shellword}\".");
+ $opt_warn_extra and $line->explain_note(
+"The wrapper framework from pkgsrc takes care that a sufficiently",
+"capable implementation of that tool will be selected.",
+"",
+"Calling the commands by their plain name instead of the macros is",
+"only available in the {pre,do,post}-* targets. For all other targets,",
+"you should still use the macros.");
+ }
+ }
+
+ checkline_mk_shellcmd_use($line, $shellword);
+
+ } elsif ($shellword =~ m"^\$\{([\w_]+)\}$" && defined($type = get_variable_type($line, $1)) && $type->basic_type eq "ShellCommand") {
+ checkline_mk_shellcmd_use($line, $shellword);
+
+ } elsif ($shellword =~ m"^\$\{(\w+)\}$" && defined($pkgctx_vardef) && exists($pkgctx_vardef->{$1})) {
+ # When the package author has explicitly
+ # defined a command variable, assume it
+ # to be valid.
+
+ } elsif ($shellword =~ m"^(?:\(|\)|:|;|;;|&&|\|\||\{|\}|break|case|cd|continue|do|done|elif|else|esac|eval|exec|exit|export|fi|for|if|read|set|shift|then|umask|unset|while)$") {
+ # Shell builtins are fine.
+
+ } elsif ($shellword =~ m"^[\w_]+=.*$") {
+ # Variable assignment.
+
+ } elsif ($shellword =~ m"^\./.*$") {
+ # All commands from the current directory are fine.
+
+ } elsif ($shellword =~ m"^#") {
+ my $semicolon = ($shellword =~ m";");
+ my $multiline = ($line->lines =~ m"--");
+
+ if ($semicolon) {
+ $line->log_warning("A shell comment should not contain semicolons.");
+ }
+ if ($multiline) {
+ $line->log_warning("A shell comment does not stop at the end of line.");
+ }
+
+ if ($semicolon || $multiline) {
+ $line->explain_warning(
+"When you split a shell command into multiple lines that are continued",
+"with a backslash, they will nevertheless be converted to a single line",
+"before the shell sees them. That means that even if it _looks_ like that",
+"the comment only spans one line in the Makefile, in fact it spans until",
+"the end of the whole shell command. To insert a comment into shell code,",
+"you can pass it as an argument to the \${SHCOMMENT} macro, which expands",
+"to a command doing nothing. Note that any special characters are",
+"nevertheless interpreted by the shell.");
+ }
+
+ } else {
+ $opt_warn_extra and $line->log_warning("Unknown shell command \"${shellword}\".");
+ $opt_warn_extra and $line->explain_warning(
+"If you want your package to be portable to all platforms that pkgsrc",
+"supports, you should only use shell commands that are covered by the",
+"tools framework.");
+
+ }
+ }
+
+ if ($state == SCST_COND && $shellword eq "cd") {
+ $line->log_error("The Solaris /bin/sh cannot handle \"cd\" inside conditionals.");
+ $line->explain_error(
+"When the Solaris shell is in \"set -e\" mode and \"cd\" fails, the",
+"shell will exit, no matter if it is protected by an \"if\" or the",
+"\"||\" operator.");
+ }
+
+ if (($state != SCST_PAX_S && $state != SCST_SED_E && $state != SCST_CASE_LABEL)) {
+ checkline_mk_absolute_pathname($line, $shellword);
+ }
+
+ if (($state == SCST_INSTALL_D || $state == SCST_MKDIR) && $shellword =~ m"^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/") {
+ $line->log_warning("Please use AUTO_MKDIRS instead of "
+ . (($state == SCST_MKDIR) ? "\${MKDIR}" : "\${INSTALL} -d")
+ . ".");
+ $line->explain_warning(
+"Setting AUTO_MKDIRS=yes automatically creates all directories that are",
+"mentioned in the PLIST. If you need additional directories, specify",
+"them in INSTALLATION_DIRS, which is a list of directories relative to",
+"\${PREFIX}.");
+ }
+
+ if (($state == SCST_INSTALL_DIR || $state == SCST_INSTALL_DIR2) && $shellword !~ regex_mk_shellvaruse && $shellword =~ m"^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)") {
+ my ($dirname) = ($1);
+
+ $line->log_note("You can use AUTO_MKDIRS=yes or INSTALLATION_DIRS+= ${dirname} instead of this command.");
+ $line->explain_note(
+"This saves you some typing. You also don't have to think about which of",
+"the many INSTALL_*_DIR macros is appropriate, since INSTALLATION_DIRS",
+"takes care of that.",
+"",
+"Note that you should only do this if the package creates _all_",
+"directories it needs before trying to install files into them.",
+"",
+"Many packages include a list of all needed directories in their PLIST",
+"file. In that case, you can just set AUTO_MKDIRS=yes and be done.");
+ }
+
+ if ($state == SCST_INSTALL_DIR2 && $shellword =~ m"^\$") {
+ $line->log_warning("The INSTALL_*_DIR commands can only handle one directory at a time.");
+ $line->explain_warning(
+"Many implementations of install(1) can handle more, but pkgsrc aims at",
+"maximum portability.");
+ }
+
+ if ($state == SCST_PAX && $shellword eq "-pe") {
+ $line->log_warning("Please use the -pp option to pax(1) instead of -pe.");
+ $line->explain_warning(
+"The -pe option tells pax to preserve the ownership of the files, which",
+"means that the installed files will belong to the user that has built",
+"the package. That's a Bad Thing.");
+ }
+
+ if ($state == SCST_PAX_S || $state == SCST_SED_E) {
+ if (false && $shellword !~ m"^[\"\'].*[\"\']$") {
+ $line->log_warning("Substitution commands like \"${shellword}\" should always be quoted.");
+ $line->explain_warning(
+"Usually these substitution commands contain characters like '*' or",
+"other shell metacharacters that might lead to lookup of matching",
+"filenames and then expand to more than one word.");
+ }
+ }
+
+ if ($state == SCST_ECHO && $shellword eq "-n") {
+ $line->log_warning("Please use \${ECHO_N} instead of \"echo -n\".");
+ }
+
+ if ($opt_warn_extra && $state != SCST_CASE_LABEL_CONT && $shellword eq "|") {
+ $line->log_warning("The exitcode of the left-hand-side command of the pipe operator is ignored.");
+ $line->explain_warning(
+"If you need to detect the failure of the left-hand-side command, use",
+"temporary files to save the output of the command.");
+ }
+
+ if ($opt_warn_extra && $shellword eq ";" && $state != SCST_COND_CONT && $state != SCST_FOR_CONT && !$set_e_mode) {
+ $line->log_warning("Please switch to \"set -e\" mode before using a semicolon to separate commands.");
+ $line->explain_warning(
+"Older versions of the NetBSD make(1) had run the shell commands using",
+"the \"-e\" option of /bin/sh. In 2004, this behavior has been changed to",
+"follow the POSIX conventions, which is to not use the \"-e\" option.",
+"The consequence of this change is that shell programs don't terminate",
+"as soon as an error occurs, but try to continue with the next command.",
+"Imagine what would happen for these commands:",
+" cd \"\$HOME\"; cd /nonexistent; rm -rf *",
+"To fix this warning, either insert \"set -e\" at the beginning of this",
+"line or use the \"&&\" operator instead of the semicolon.");
+ }
+
+ #
+ # State transition.
+ #
+
+ if ($state == SCST_SET && $shellword =~ m"^-.*e") {
+ $set_e_mode = true;
+ }
+ if ($state == SCST_START && $shellword eq "\${RUN}") {
+ $set_e_mode = true;
+ }
+
+ $state = ($shellword eq ";;") ? SCST_CASE_LABEL
+ # Note: The order of the following two lines is important.
+ : ($state == SCST_CASE_LABEL_CONT && $shellword eq "|") ? SCST_CASE_LABEL
+ : ($shellword =~ m"^[;&\|]+$") ? SCST_START
+ : ($state == SCST_START) ? (
+ ($shellword eq "\${INSTALL}") ? SCST_INSTALL
+ : ($shellword eq "\${MKDIR}") ? SCST_MKDIR
+ : ($shellword eq "\${PAX}") ? SCST_PAX
+ : ($shellword eq "\${SED}") ? SCST_SED
+ : ($shellword eq "\${ECHO}") ? SCST_ECHO
+ : ($shellword eq "\${RUN}") ? SCST_START
+ : ($shellword eq "echo") ? SCST_ECHO
+ : ($shellword eq "set") ? SCST_SET
+ : ($shellword =~ m"^(?:if|elif|while)$") ? SCST_COND
+ : ($shellword =~ m"^(?:then|else|do)$") ? SCST_START
+ : ($shellword eq "case") ? SCST_CASE
+ : ($shellword eq "for") ? SCST_FOR
+ : ($shellword eq "(") ? SCST_START
+ : ($shellword =~ m"^\$\{INSTALL_[A-Z]+_DIR\}$") ? SCST_INSTALL_DIR
+ : ($shellword =~ regex_sh_varassign) ? SCST_START
+ : SCST_CONT)
+ : ($state == SCST_MKDIR) ? SCST_MKDIR
+ : ($state == SCST_INSTALL && $shellword eq "-d") ? SCST_INSTALL_D
+ : ($state == SCST_INSTALL || $state == SCST_INSTALL_D) ? (
+ ($shellword =~ m"^-[ogm]$") ? SCST_CONT
+ : $state)
+ : ($state == SCST_INSTALL_DIR) ? (
+ ($shellword =~ m"^-") ? SCST_CONT
+ : ($shellword =~ m"^\$") ? SCST_INSTALL_DIR2
+ : $state)
+ : ($state == SCST_INSTALL_DIR2) ? $state
+ : ($state == SCST_PAX) ? (
+ ($shellword eq "-s") ? SCST_PAX_S
+ : ($shellword =~ m"^-") ? SCST_PAX
+ : SCST_CONT)
+ : ($state == SCST_PAX_S) ? SCST_PAX
+ : ($state == SCST_SED) ? (
+ ($shellword eq "-e") ? SCST_SED_E
+ : ($shellword =~ m"^-") ? SCST_SED
+ : SCST_CONT)
+ : ($state == SCST_SED_E) ? SCST_SED
+ : ($state == SCST_SET) ? SCST_SET_CONT
+ : ($state == SCST_SET_CONT) ? SCST_SET_CONT
+ : ($state == SCST_CASE) ? SCST_CASE_IN
+ : ($state == SCST_CASE_IN && $shellword eq "in") ? SCST_CASE_LABEL
+ : ($state == SCST_CASE_LABEL && $shellword eq "esac") ? SCST_CONT
+ : ($state == SCST_CASE_LABEL) ? SCST_CASE_LABEL_CONT
+ : ($state == SCST_CASE_LABEL_CONT && $shellword eq ")") ? SCST_START
+ : ($state == SCST_CONT) ? SCST_CONT
+ : ($state == SCST_COND) ? SCST_COND_CONT
+ : ($state == SCST_COND_CONT) ? SCST_COND_CONT
+ : ($state == SCST_FOR) ? SCST_FOR_IN
+ : ($state == SCST_FOR_IN && $shellword eq "in") ? SCST_FOR_CONT
+ : ($state == SCST_FOR_CONT) ? SCST_FOR_CONT
+ : ($state == SCST_ECHO) ? SCST_CONT
+ : do {
+ $line->log_warning("[" . scst_statename->[$state] . " ${shellword}] Keeping the current state.");
+ $state;
+ };
+ }
+
+ if ($rest !~ m"^\s*$") {
+ $line->log_error("Internal pkglint error: " . scst_statename->[$state] . ": rest=${rest}");
+ }
+}
+
+sub checkline_mk_shellcmd($$) {
+ my ($line, $shellcmd) = @_;
+
+ checkline_mk_text($line, $shellcmd);
+ checkline_mk_shelltext($line, $shellcmd);
+}
+
diff --git a/pkgtools/pkglint4/files/PkgLint/SimpleMatch.pm b/pkgtools/pkglint4/files/PkgLint/SimpleMatch.pm
new file mode 100644
index 00000000000..a4934372758
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/SimpleMatch.pm
@@ -0,0 +1,44 @@
+# $NetBSD: SimpleMatch.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# A SimpleMatch is the result of applying a regular expression to a Perl
+# scalar value. It can return the range and the text of the captured
+# groups.
+#
+package PkgLint::SimpleMatch;
+
+use strict;
+use warnings;
+
+use enum qw(STRING STARTS ENDS N);
+
+sub new($$) {
+ my ($class, $string, $starts, $ends) = @_;
+ my ($self) = ([$string, [@{$starts}], [@{$ends}], $#{$ends}]);
+ bless($self, $class);
+ return $self;
+}
+
+sub string($) { return shift()->[STRING]; }
+sub n($) { return shift()->[N]; }
+
+sub has($$) {
+ my ($self, $n) = @_;
+
+ return 0 <= $n && $n <= $self->n
+ && defined($self->[STARTS]->[$n])
+ && defined($self->[ENDS]->[$n]);
+}
+
+sub text($$) {
+ my ($self, $n) = @_;
+
+ my $start = $self->[STARTS]->[$n];
+ my $end = $self->[ENDS]->[$n];
+ return substr($self->string, $start, $end - $start);
+}
+
+sub range($$) {
+ my ($self, $n) = @_;
+
+ return ($self->[STARTS]->[$n], $self->[ENDS]->[$n]);
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/SubstContext.pm b/pkgtools/pkglint4/files/PkgLint/SubstContext.pm
new file mode 100644
index 00000000000..f21e57546d0
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/SubstContext.pm
@@ -0,0 +1,198 @@
+# $NetBSD: SubstContext.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# This class records the state of a block of variable assignments that make
+# up a SUBST class. As these variable assignments are not easy to get right
+# unless you do it every day, and the possibility of typos is high, pkglint
+# provides additional checks for them.
+#
+package PkgLint::SubstContext;
+
+use strict;
+use warnings;
+
+BEGIN {
+ import PkgLint::Util qw(
+ false true
+ );
+ import PkgLint::Logging qw(
+ log_warning
+ );
+}
+
+use enum qw(:SUBST_ ID CLASS STAGE MESSAGE FILES SED VARS FILTER_CMD);
+
+sub new($) {
+ my ($class) = @_;
+ my ($self) = ([undef, undef, undef, undef, [], [], [], undef]);
+ bless($self, $class);
+ return $self;
+}
+
+sub subst_class($) { return shift()->[SUBST_CLASS]; }
+sub subst_stage($) { return shift()->[SUBST_STAGE]; }
+sub subst_message($) { return shift()->[SUBST_MESSAGE]; }
+sub subst_files($) { return shift()->[SUBST_FILES]; }
+sub subst_sed($) { return shift()->[SUBST_SED]; }
+sub subst_vars($) { return shift()->[SUBST_VARS]; }
+sub subst_filter_cmd($) { return shift()->[SUBST_FILTER_CMD]; }
+sub subst_id($) { return shift()->[SUBST_ID]; }
+
+sub init($) {
+ my ($self) = @_;
+
+ $self->[SUBST_ID] = undef;
+ $self->[SUBST_CLASS] = undef;
+ $self->[SUBST_STAGE] = undef;
+ $self->[SUBST_MESSAGE] = undef;
+ $self->[SUBST_FILES] = [];
+ $self->[SUBST_SED] = [];
+ $self->[SUBST_VARS] = [];
+ $self->[SUBST_FILTER_CMD] = undef;
+}
+
+sub check_end($$) {
+ my ($self, $line) = @_;
+
+ return unless defined($self->subst_id);
+
+ if (!defined($self->subst_class)) {
+ $main::opt_warn_extra and $line->log_warning("Incomplete SUBST block: SUBST_CLASSES missing.");
+ }
+ if (!defined($self->subst_stage)) {
+ $main::opt_warn_extra and $line->log_warning("Incomplete SUBST block: SUBST_STAGE missing.");
+ }
+ if (@{$self->subst_files} == 0) {
+ $main::opt_warn_extra and $line->log_warning("Incomplete SUBST block: SUBST_FILES missing.");
+ }
+ if (@{$self->subst_sed} == 0 && @{$self->subst_vars} == 0 && !defined($self->subst_filter_cmd)) {
+ $main::opt_warn_extra and $line->log_warning("Incomplete SUBST block: SUBST_SED or SUBST_VARS missing.");
+ }
+ $self->init();
+}
+
+sub is_complete($) {
+ my ($self) = @_;
+
+ return false unless defined($self->subst_id);
+ return false unless defined($self->subst_class);
+ return false unless defined($self->subst_files);
+ return false if @{$self->subst_sed} == 0 && @{$self->subst_vars} == 0;
+ return true;
+}
+
+sub check_varassign($$$$$) {
+ my ($self, $line, $varname, $op, $value) = @_;
+ my ($varbase, $varparam, $id);
+
+ if ($varname eq "SUBST_CLASSES") {
+
+ if ($value =~ m"^(\S+)\s") {
+ $main::opt_warn_extra and $line->log_warning("Please add only one class at a time to SUBST_CLASSES.");
+ $self->[SUBST_CLASS] = $1;
+ $self->[SUBST_ID] = $1;
+
+ } else {
+ if (defined($self->subst_class)) {
+ $main::opt_warn_extra and $line->log_warning("SUBST_CLASSES should only appear once in a SUBST block.");
+ }
+ $self->[SUBST_CLASS] = $value;
+ $self->[SUBST_ID] = $value;
+ }
+ return;
+ }
+
+ $id = $self->subst_id;
+
+ if ($varname =~ m"^(SUBST_(?:STAGE|MESSAGE|FILES|SED|VARS|FILTER_CMD))\.([\-\w_]+)$") {
+ ($varbase, $varparam) = ($1, $2);
+
+ if (!defined($id)) {
+ $main::opt_warn_extra and $line->log_note("SUBST_CLASSES should precede the definition of ${varbase}.${varparam}.");
+
+ $id = $self->[SUBST_ID] = $varparam;
+ }
+ } else {
+ if (defined($id)) {
+ $main::opt_warn_extra and $line->log_warning("Foreign variable in SUBST block.");
+ }
+ return;
+ }
+
+ if ($varparam ne $id) {
+
+ # XXX: This code sometimes produces weird warnings. See
+ # meta-pkgs/xorg/Makefile.common 1.41 for an example.
+ if ($self->is_complete()) {
+ $self->check_end($line);
+
+ # The following assignment prevents an additional warning,
+ # but from a technically viewpoint, it is incorrect.
+ $self->[SUBST_CLASS] = $varparam;
+ $self->[SUBST_ID] = $varparam;
+ $id = $varparam;
+ } else {
+ $main::opt_warn_extra and $line->log_warning("Variable parameter \"${varparam}\" does not match SUBST class \"${id}\".");
+ }
+ }
+
+ if ($varbase eq "SUBST_STAGE") {
+ if (defined($self->subst_stage)) {
+ $main::opt_warn_extra and $line->log_warning("Duplicate definition of SUBST_STAGE.${id}.");
+ } else {
+ $self->[SUBST_STAGE] = $value;
+ }
+
+ } elsif ($varbase eq "SUBST_MESSAGE") {
+ if (defined($self->subst_message)) {
+ $main::opt_warn_extra and $line->log_warning("Duplicate definition of SUBST_MESSAGE.${id}.");
+ } else {
+ $self->[SUBST_MESSAGE] = $value;
+ }
+
+ } elsif ($varbase eq "SUBST_FILES") {
+ if (@{$self->subst_files} > 0) {
+ if ($op ne "+=") {
+ $main::opt_warn_extra and $line->log_warning("All but the first SUBST_FILES line should use the \"+=\" operator.");
+ }
+ }
+ push(@{$self->subst_files}, $value);
+
+ } elsif ($varbase eq "SUBST_SED") {
+ if (@{$self->subst_sed} > 0) {
+ if ($op ne "+=") {
+ $main::opt_warn_extra and $line->log_warning("All but the first SUBST_SED line should use the \"+=\" operator.");
+ }
+ }
+ push(@{$self->subst_sed}, $value);
+
+ } elsif ($varbase eq "SUBST_FILTER_CMD") {
+ if (defined($self->subst_filter_cmd)) {
+ $main::opt_warn_extra and $line->log_warning("Duplicate definition of SUBST_FILTER_CMD.${id}.");
+ } else {
+ $self->[SUBST_FILTER_CMD] = $value;
+ }
+
+ } elsif ($varbase eq "SUBST_VARS") {
+ if (@{$self->subst_vars} > 0) {
+ if ($op ne "+=") {
+ $main::opt_warn_extra and $line->log_warning("All but the first SUBST_VARS line should use the \"+=\" operator.");
+ }
+ }
+ push(@{$self->subst_vars}, $value);
+
+ } else {
+ $main::opt_warn_extra and $line->log_warning("Foreign variable in SUBST block.");
+ }
+}
+
+sub to_string($) {
+ my ($self) = @_;
+
+ return sprintf("SubstContext(%s %s %s %s %s %s)",
+ (defined($self->subst_class) ? $self->subst_class : "(undef)"),
+ (defined($self->subst_stage) ? $self->subst_stage : "(undef)"),
+ (defined($self->subst_message) ? $self->subst_message : "(undef)"),
+ scalar(@{$self->subst_files}),
+ scalar(@{$self->subst_sed}),
+ (defined($self->subst_id) ? $self->subst_id : "(undef)"));
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/Type.pm b/pkgtools/pkglint4/files/PkgLint/Type.pm
new file mode 100644
index 00000000000..1eb7cd2a74c
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Type.pm
@@ -0,0 +1,102 @@
+# $NetBSD: Type.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# A Type in pkglint is a combination of a data type and a permission
+# specification. Further details can be found in the chapter ``The pkglint
+# type system'' of the pkglint book.
+#
+package PkgLint::Type;
+
+use strict;
+use warnings;
+
+BEGIN {
+ import PkgLint::Util qw(
+ false true
+ );
+ import PkgLint::Logging qw(
+ log_warning NO_LINES
+ );
+ use Exporter;
+ use vars qw(@ISA @EXPORT_OK);
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ LK_NONE LK_INTERNAL LK_EXTERNAL
+ GUESSED NOT_GUESSED
+ );
+}
+
+use enum qw(KIND_OF_LIST BASIC_TYPE ACLS IS_GUESSED);
+use enum qw(:LK_ NONE INTERNAL EXTERNAL);
+use enum qw(:ACLE_ SUBJECT_RE PERMS);
+use enum qw(NOT_GUESSED GUESSED);
+
+sub new($$$) {
+ my ($class, $kind_of_list, $basic_type, $acls, $guessed) = @_;
+ my ($self) = ([$kind_of_list, $basic_type, $acls, $guessed]);
+ bless($self, $class);
+ return $self;
+}
+
+sub kind_of_list($) { return shift()->[KIND_OF_LIST]; }
+sub basic_type($) { return shift()->[BASIC_TYPE]; }
+# no getter method for acls
+sub is_guessed($) { return shift()->[IS_GUESSED]; }
+
+sub perms($$) {
+ my ($self, $fname) = @_;
+ my ($perms);
+
+ foreach my $acl_entry (@{$self->[ACLS]}) {
+ if ($fname =~ $acl_entry->[ACLE_SUBJECT_RE]) {
+ return $acl_entry->[ACLE_PERMS];
+ }
+ }
+ return;
+}
+
+# Returns the union of all possible permissions. This can be used to
+# check whether a variable may be defined or used at all, or if it is
+# read-only.
+sub perms_union($) {
+ my ($self) = @_;
+ my ($perms);
+
+ $perms = "";
+ foreach my $acl_entry(@{$self->[ACLS]}) {
+ $perms .= $acl_entry->[ACLE_PERMS];
+ }
+ return $perms;
+}
+
+# Returns whether the type is considered an external list. All external
+# lists are, of course, as well as some other data types that are not
+# defined as lists to make the implementation of checkline_mk_vartype
+# easier.
+sub is_practically_a_list($) {
+ my ($self) = @_;
+
+ return ($self->kind_of_list == LK_EXTERNAL) ? true
+ : ($self->kind_of_list == LK_INTERNAL) ? false
+ : ($self->basic_type eq "BuildlinkPackages") ? true
+ : ($self->basic_type eq "SedCommands") ? true
+ : ($self->basic_type eq "ShellCommand") ? true
+ : false;
+}
+
+# Returns whether variables of this type may be extended using the "+="
+# operator.
+sub may_use_plus_eq($) {
+ my ($self) = @_;
+
+ return ($self->kind_of_list != LK_NONE) ? true
+ : ($self->basic_type eq "AwkCommand") ? true
+ : ($self->basic_type eq "BuildlinkPackages") ? true
+ : ($self->basic_type eq "SedCommands") ? true
+ : false;
+}
+
+sub to_string($) {
+ my ($self) = @_;
+
+ return (["", "InternalList of ", "List of "]->[$self->kind_of_list]) . $self->basic_type;
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/Util.pm b/pkgtools/pkglint4/files/PkgLint/Util.pm
new file mode 100644
index 00000000000..350f96f6b65
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/Util.pm
@@ -0,0 +1,97 @@
+# $NetBSD: Util.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# This package is a catch-all for subroutines that are not application-spe-
+# cific. Currently it contains the boolean constants C<false> and C<true>,
+# as well as a function to print text in a table format, and a function
+# that converts an array into a hash. The latter is just for convenience
+# because I don't know of a Perl operator similar to qw() that can be used
+# for creating a hash.
+#
+package PkgLint::Util;
+
+use strict;
+use warnings;
+
+BEGIN {
+ use Exporter;
+ use vars qw(@ISA @EXPORT_OK);
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ assert
+ false true dont_know doesnt_matter
+ array_to_hash normalize_pathname print_table
+ );
+}
+
+use enum qw(false true dont_know doesnt_matter);
+
+sub assert($$) {
+ my ($cond, $msg) = @_;
+ my (@callers, $n);
+
+ if (!$cond) {
+ print STDERR ("FATAL: Assertion failed: ${msg}.\n");
+
+ for ($n = 0; my @info = caller($n); $n++) {
+ push(@callers, [$info[2], $info[3]]);
+ }
+
+ for (my $i = $#callers; $i >= 0; $i--) {
+ my $info = $callers[$i];
+ printf STDERR (" line %4d called %s\n", $info->[0], $info->[1]);
+ }
+ exit(1);
+ }
+}
+
+# Prints the C<$table> on the C<$out> stream. The C<$table> shall be an
+# array of rows, each row shall be an array of cells, and each cell shall
+# be a string.
+sub print_table($$) {
+ my ($out, $table) = @_;
+ my (@width) = ();
+ foreach my $row (@{$table}) {
+ foreach my $i (0..$#{$row}) {
+ if (!defined($width[$i]) || length($row->[$i]) > $width[$i]) {
+ $width[$i] = length($row->[$i]);
+ }
+ }
+ }
+ foreach my $row (@{$table}) {
+ my ($max) = ($#{$row});
+ foreach my $i (0..$max) {
+ if ($i != 0) {
+ print $out (" ");
+ }
+ print $out ($row->[$i]);
+ if ($i != $max) {
+ print $out (" " x ($width[$i] - length($row->[$i])));
+ }
+ }
+ print $out ("\n");
+ }
+}
+
+sub array_to_hash(@) {
+ my ($result) = {};
+
+ foreach my $arg (@_) {
+ $result->{$arg} = 1;
+ }
+ return $result;
+}
+
+sub normalize_pathname($) {
+ my ($fname) = @_;
+
+ # strip "." path components
+ $fname =~ s,^(?:\./)+,,;
+ $fname =~ s,/(?:\./)+,/,g;
+ $fname =~ s,/+,/,g;
+
+ # strip intermediate "../.." path components
+ while ($fname =~ s,/[^.][^/]*/[^.][^/]*/\.\./\.\./,/,) {
+ }
+
+ return $fname;
+}
diff --git a/pkgtools/pkglint4/files/PkgLint/VarUseContext.pm b/pkgtools/pkglint4/files/PkgLint/VarUseContext.pm
new file mode 100644
index 00000000000..8311624340b
--- /dev/null
+++ b/pkgtools/pkglint4/files/PkgLint/VarUseContext.pm
@@ -0,0 +1,67 @@
+# The various contexts in which make(1) variables can appear in pkgsrc.
+# Further details can be found in the chapter ``The pkglint type system''
+# of the pkglint book.
+#
+package PkgLint::VarUseContext;
+
+use strict;
+use warnings;
+
+BEGIN {
+ import PkgLint::Util qw(
+ false true
+ );
+ import PkgLint::Logging qw(
+ log_warning NO_LINES
+ );
+ use Exporter;
+ use vars qw(@ISA @EXPORT_OK);
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ VUC_TIME_UNKNOWN VUC_TIME_LOAD VUC_TIME_RUN
+ VUC_TYPE_UNKNOWN
+ VUC_SHELLWORD_UNKNOWN VUC_SHELLWORD_PLAIN VUC_SHELLWORD_DQUOT
+ VUC_SHELLWORD_SQUOT VUC_SHELLWORD_BACKT VUC_SHELLWORD_FOR
+ VUC_EXTENT_UNKNOWN VUC_EXTENT_FULL VUC_EXTENT_WORD
+ VUC_EXTENT_WORD_PART
+ );
+}
+
+use enum qw(TIME TYPE SHELLWORD EXTENT);
+use enum qw(:VUC_TIME_ UNKNOWN LOAD RUN);
+use constant VUC_TYPE_UNKNOWN => undef;
+use enum qw(:VUC_SHELLWORD_ UNKNOWN PLAIN DQUOT SQUOT BACKT FOR);
+use enum qw(:VUC_EXTENT_ UNKNOWN FULL WORD WORD_PART);
+
+my $pool = {};
+
+sub new($$$$$) {
+ my ($class, $time, $type, $shellword, $extent) = @_;
+ my ($self) = ([$time, $type, $shellword, $extent]);
+ bless($self, $class);
+ return $self;
+}
+sub new_from_pool($$$$$) {
+ my ($class, $time, $type, $shellword, $extent) = @_;
+ my $key = "${time}-${type}-${shellword}-${extent}";
+
+ if (!exists($pool->{$key})) {
+ $pool->{$key} = $class->new($time, $type, $shellword, $extent);
+ }
+ return $pool->{$key};
+}
+
+sub time($) { return shift()->[TIME]; }
+sub type($) { return shift()->[TYPE]; }
+sub shellword($) { return shift()->[SHELLWORD]; }
+sub extent($) { return shift()->[EXTENT]; }
+
+sub to_string($) {
+ my ($self) = @_;
+
+ return sprintf("(%s %s %s %s)",
+ ["unknown-time", "load-time", "run-time"]->[$self->time],
+ (defined($self->type) ? $self->type->to_string() : "no-type"),
+ ["none", "plain", "squot", "dquot", "backt", "for"]->[$self->shellword],
+ ["unknown", "full", "word", "word-part"]->[$self->extent]);
+}
diff --git a/pkgtools/pkglint4/files/build.pl b/pkgtools/pkglint4/files/build.pl
new file mode 100644
index 00000000000..e58b4e90ae4
--- /dev/null
+++ b/pkgtools/pkglint4/files/build.pl
@@ -0,0 +1,25 @@
+#! @PERL@
+# $NetBSD: build.pl,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+use strict;
+use warnings;
+
+sub readfile {
+ my $file = shift;
+
+ local $/ = undef;
+ open(my $in, "<", $file) || die "failed to read $file: $!";
+ my $contents = <$in>;
+ close($in) || die "failed to read $file: $!";
+
+ return $contents;
+}
+
+while (my $line = <>) {
+ if ($line =~ m"^#include (.+)$/") {
+ print readfile($1);
+ } else {
+ print $line;
+ }
+}
diff --git a/pkgtools/pkglint4/files/deprecated.map b/pkgtools/pkglint4/files/deprecated.map
new file mode 100644
index 00000000000..f8b25c4d16c
--- /dev/null
+++ b/pkgtools/pkglint4/files/deprecated.map
@@ -0,0 +1,166 @@
+# $NetBSD: deprecated.map,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+# This file contains names of Makefile variables and a short explanation
+# what to do to make the warning disappear. Lines should only be removed
+# if the explanation changes, in which case the new explanation should
+# be added to the current date.
+
+# December 2003
+FIX_RPATH It has been removed from pkgsrc in 2003.
+
+# February 2005
+ALL_TARGET Use BUILD_TARGET instead.
+NO_WRKSUBDIR Use WRKSRC=${WRKDIR} instead.
+LIB_DEPENDS Use DEPENDS instead.
+MASTER_SITE_SUBDIR Use ${MASTER_SITE_FOO:=subdir/} instead.
+PATCH_SITE_SUBDIR Use ${PATCH_SITES_FOO:=subdir/} instead.
+ONLY_FOR_ARCHS Use ONLY_FOR_PLATFORM instead.
+NOT_FOR_ARCHS Use NOT_FOR_PLATFORM instead.
+ONLY_FOR_OPSYS Use ONLY_FOR_PLATFORM instead.
+NOT_FOR_OPSYS Use NOT_FOR_PLATFORM instead.
+
+# May 2005
+ALL_TARGET Use BUILD_TARGET instead.
+DIGEST_FILE Use DISTINFO_FILE instead.
+IGNORE Use PKG_FAIL_REASON or PKG_SKIP_REASON instead.
+IS_INTERACTIVE Use INTERACTIVE_STAGE instead.
+KERBEROS Use the PKG_OPTIONS framework instead.
+MASTER_SITE_SUBDIR Use some form of MASTER_SITES instead.
+MD5_FILE Use DISTINFO_FILE instead.
+MIRROR_DISTFILE Use NO_BIN_ON_FTP and/or NO_SRC_ON_FTP instead.
+NO_CDROM Use NO_BIN_ON_CDROM and/or NO_SRC_ON_CDROM instead.
+NO_PATCH You can just remove it.
+NO_WRKSUBDIR Use WRKSRC=${WRKDIR} instead.
+PATCH_SITE_SUBDIR Use some form of PATCHES_SITES instead.
+PATCH_SUM_FILE Use DISTINFO_FILE instead.
+PKG_JVM Use PKG_DEFAULT_JVM instead.
+USE_BUILDLINK2 You can just remove it.
+USE_BUILDLINK3 You can just remove it.
+USE_CANNA Use the PKG_OPTIONS framework instead.
+USE_DB4 Use the PKG_OPTIONS framework instead.
+USE_DIRS You can just remove it.
+USE_ESOUND Use the PKG_OPTIONS framework instead.
+USE_GIF Use the PKG_OPTIONS framework instead.
+USE_GMAKE Use USE_TOOLS+=gmake instead.
+USE_GNU_TOOLS Use USE_TOOLS instead.
+USE_IDEA Use the PKG_OPTIONS framework instead.
+USE_LIBCRACK Use the PKG_OPTIONS framework instead.
+USE_MMX Use the PKG_OPTIONS framework instead.
+USE_PKGLIBTOOL Use USE_LIBTOOL instead.
+USE_SSL Include "../../security/openssl/buildlink3.mk" instead.
+
+# July 2005
+USE_PERL5 Use USE_TOOLS+=perl or USE_TOOLS+=perl:run instead.
+
+# October 2005
+NO_TOOLS You can just remove it.
+NO_WRAPPER You can just remove it.
+
+# November 2005
+ALLFILES Use CKSUMFILES instead.
+DEPENDS_TARGET Use DEPENDS instead.
+FETCH_DEPENDS Use DEPENDS instead.
+RUN_DEPENDS Use DEPENDS instead.
+
+# December 2005
+USE_CUPS Use the PKG_OPTIONS framework (option cups) instead.
+USE_I586 Use the PKG_OPTIONS framework (option i586) instead.
+USE_INN Use the PKG_OPTIONS framework instead.
+USE_OPENLDAP Use the PKG_OPTIONS framework (option openldap) instead.
+USE_OSS Use the PKG_OPTIONS framework (option oss) instead.
+USE_RSAREF2 Use the PKG_OPTIONS framework (option rsaref) instead.
+USE_SASL Use the PKG_OPTIONS framework (option sasl) instead.
+USE_SASL2 Use the PKG_OPTIONS framework (option sasl) instead.
+USE_SJ3 Use the PKG_OPTIONS framework (option sj3) instead.
+USE_SOCKS Use the PKG_OPTIONS framework (socks4 and socks5 options) instead.
+USE_WNN4 Use the PKG_OPTIONS framework (option wnn4) instead.
+USE_XFACE Use the PKG_OPTIONS framework instead.
+
+# February 2006
+TOOLS_DEPMETHOD Use the :build or :run modifiers in USE_TOOLS instead.
+MANDIR Please use ${PREFIX}/${PKGMANDIR} instead.
+DOWNLOADED_DISTFILE Use the shell variable $$extract_file instead.
+DECOMPRESS_CMD Use EXTRACT_CMD instead.
+
+# March 2006
+INSTALL_EXTRA_TMPL Use INSTALL_TEMPLATE instead.
+DEINSTALL_EXTRA_TMPL Use DEINSTALL_TEMPLATE instead.
+
+# April 2006
+RECOMMENDED Use ABI_DEPENDS instead.
+BUILD_USES_MSGFMT Use USE_TOOLS+=msgfmt instead.
+USE_MSGFMT_PLURALS Use USE_TOOLS+=msgfmt instead.
+
+# May 2006
+EXTRACT_USING_PAX Use "EXTRACT_OPTS=-t pax" instead.
+NO_EXTRACT It doesn't exist anymore.
+_FETCH_MESSAGE Use FETCH_MESSAGE (different format) instead.
+BUILDLINK_DEPENDS.* Use BUILDLINK_API_DEPENDS.* instead.
+BUILDLINK_RECOMMENDED.* Use BUILDLINK_ABI_DEPENDS.* instead.
+SHLIB_HANDLING Use CHECK_SHLIBS_SUPPORTED instead.
+USE_RMAN It has been removed.
+
+# June 2006
+DEINSTALL_SRC Use the pkginstall framework instead.
+INSTALL_SRC Use the pkginstall framework instead.
+DEINSTALL_TEMPLATE Use DEINSTALL_TEMPLATES instead.
+INSTALL_TEMPLATE Use INSTALL_TEMPLATES instead.
+HEADER_TEMPLATE Use HEADER_TEMPLATES instead.
+_REPLACE.* Use REPLACE.* instead.
+_REPLACE_FILES.* Use REPLACE_FILES.* instead.
+MESSAGE Use MESSAGE_SRC instead.
+INSTALL_FILE It may only be used internally by pkgsrc.
+DEINSTALL_FILE It may only be used internally by pkgsrc.
+
+# July 2006
+USE_DIGEST You can just remove it.
+LTCONFIG_OVERRIDE You can just remove it.
+USE_GNU_GETTEXT You can just remove it.
+BUILD_ENV Use PKGSRC_MAKE_ENV instead.
+DYNAMIC_MASTER_SITES You can just remove it.
+
+# September 2006
+MAKEFILE Use MAKE_FILE instead.
+
+# November 2006
+SKIP_PORTABILITY_CHECK Use CHECK_PORTABILITY_SKIP (a list of patterns) instead.
+PKG_SKIP_REASON Use PKG_FAIL_REASON instead.
+
+# January 2007
+BUILDLINK_TRANSFORM.* Use BUILDLINK_FNAME_TRANSFORM.* instead.
+
+# March 2007
+SCRIPTDIR You can just remove it.
+NO_PKG_REGISTER You can just remove it.
+NO_DEPENDS You can just remove it.
+
+# October 2007
+_PKG_SILENT Use RUN (with more error checking) instead.
+_PKG_DEBUG Use RUN (with more error checking) instead.
+LICENCE Use LICENSE instead.
+# The following variable is not yet deprecated, as there has been
+# a large disagreement on the proper spelling.
+#ACCEPTABLE_LICENCES Use ACCEPTABLE_LICENSES instead.
+
+# November 2007
+#USE_NCURSES Include "../../devel/ncurses/buildlink3.mk" instead.
+
+# December 2007
+INSTALLATION_DIRS_FROM_PLIST Use AUTO_MKDIRS instead.
+
+# April 2009
+NO_PACKAGE It doesn't exist anymore.
+NO_MTREE You can just remove it.
+
+# July 2012
+SETGIDGAME Use USE_GAMESGROUP instead.
+GAMEGRP Use GAMES_GROUP instead.
+GAMEOWN Use GAMES_USER instead.
+
+# July 2013
+USE_GNU_READLINE Include "../../devel/readline/buildlink3.mk" instead.
+
+# October 2014
+SVR4_PKGNAME Just remove it.
+PKG_INSTALLATION_TYPES Just remove it.
diff --git a/pkgtools/pkglint4/files/doc/Makefile b/pkgtools/pkglint4/files/doc/Makefile
new file mode 100644
index 00000000000..049a20531c0
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/Makefile
@@ -0,0 +1,27 @@
+# $NetBSD: Makefile,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+XMLDOCS+= pkglint.xml
+XMLDOCS+= chap.intro.xml
+XMLDOCS+= chap.defs.xml
+XMLDOCS+= chap.types.xml
+XMLDOCS+= chap.code.xml
+XMLDOCS+= chap.statemachines.xml
+XMLDOCS+= chap.future.xml
+
+IMAGES+= statemachine.patch.png
+IMAGES+= statemachine.shellcmd.png
+
+.PHONY: all
+all: pkglint.html
+
+pkglint.html: ${XMLDOCS} ${IMAGES} stylesheet.xsl
+ xmlto -m stylesheet.xsl html-nochunks pkglint.xml
+
+.PHONY: clean
+clean:
+ rm -f *.html *.png
+
+.SUFFIXES: .dia .png
+.dia.png:
+ dia -e ${.TARGET:Q} -t png ${.IMPSRC:Q}
diff --git a/pkgtools/pkglint4/files/doc/chap.code.xml b/pkgtools/pkglint4/files/doc/chap.code.xml
new file mode 100644
index 00000000000..44b02f9f128
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.code.xml
@@ -0,0 +1,307 @@
+<!-- $NetBSD: chap.code.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="code">
+<title>Code structure</title>
+
+ <para>In this chapter, I give an overview of how the &pkglint;
+ code is organized, starting with the <function>main</function>
+ function, passing the functions that check a single line and
+ finally arriving at the infrastructure that makes writing the
+ other functions easier.</para>
+
+<sect1 id="code.overview">
+<title>Overview</title>
+
+ <para>The &pkglint; code is structured in modular, easy to
+ understand procedures. These procedures can be further
+ classified with respect to what they do. There are procedures
+ that check a file, others check the lines of a file, again
+ others check a single line. These classes of procedures are
+ described in the following sections in a top-down
+ fashion.</para>
+
+ <para>If nothing special is said about which procedures call
+ which others, you may assume that procedures of a certain rank
+ only call procedures that are of a strictly lower rank. For
+ example, no <function>checkline_*</function> will ever call
+ <function>checkfile_*</function>. Sometimes, functions of the
+ same rank are called, but these cases are documented
+ explicitly.</para>
+
+</sect1>
+
+<sect1 id="code.select">
+<title>Selecting the proper checking function</title>
+
+ <para>The <function>main</function> procedure of &pkglint; is a
+ simple loop around a TODO list containing pathnames of items (I
+ couldn't think of a better name here). The decision of which
+ checks to apply to a given item is done in
+ <function>checkitem</function>, which checks whether the item is
+ a file or a directory and dispatches the actual checking to
+ specialized procedures.</para>
+
+</sect1>
+
+<sect1 id="code.dir">
+<title>Checking a directory</title>
+
+ <para>The procedures that check a directory are
+ <function>checkdir_root</function> for the pkgsrc root
+ directory, <function>checkdir_category</function> for a category
+ of packages and <function>checkdir_package</function> for a
+ single package.</para>
+
+</sect1>
+
+<sect1 id="code.file">
+<title>Checking a file</title>
+
+ <para>Since the dispatching for files requires much code, it has
+ been put into a separate procedure called
+ <function>checkfile</function>, which further dispatches the
+ call to the other procedures.</para>
+
+ <para>The procedures that check a specific file are
+ <function>checkfile_ALTERNATIVES</function>,
+ <function>checkfile_DESCR</function>,
+ <function>checkfile_distinfo</function>,
+ <function>checkfile_extra</function>,
+ <function>checkfile_INSTALL</function>,
+ <function>checkfile_MESSAGE</function>,
+ <function>checkfile_mk</function>,
+ <function>checkfile_patch</function> and
+ <function>checkfile_PLIST</function>. For most of the
+ procedures, it should be obvious to which files they are
+ applied. A distinction is made between buildlink3 files and
+ other <filename>Makefiles</filename>, as some additional checks
+ apply to buildlink3 files. Of course, these procedures use
+ pretty much the same code for checking, and this is where the
+ <function>checklines_*</function> functions step in.</para>
+
+ <para>The <function>checkfile_package_Makefile</function>
+ function is somewhat special in that it expects four parameters
+ instead of only one. This is because loading the package data
+ has been separated from the actual checking.</para>
+
+</sect1>
+
+<sect1 id="code.lines">
+<title>Checking the lines in a file</title>
+
+ <para>This class of procedures consists of
+ <function>checklines_trailing_empty_lines</function>,
+ <function>checklines_package_Makefile_varorder</function> and
+ <function>checklines_mk</function>. The middle one is too
+ complex to be included in
+ <function>checkfile_package_Makefile</function>, and the other
+ ones are of so generic use that they deserved to be procedures
+ of their own.</para>
+
+ <para>The <function>checklines_mk</function> makes heavy use of
+ the various <function>checkline_*</function> functions that are
+ explained in the next chapter.</para>
+
+</sect1>
+
+<sect1 id="code.line">
+<title>Checking a single line in a file</title>
+
+ <para>This class of procedures checks a single line of a file.
+ The number of parameters differs for most of these procedures,
+ as some need more context information and others don't.</para>
+
+ <para>The procedures that are applicable to any file type are
+ <function>checkline_length</function>,
+ <function>checkline_valid_characters</function>,
+ <function>checkline_valid_characters_in_variable</function>,
+ <function>checkline_trailing_whitespace</function>,
+ <function>checkline_rcsid_regex</function>,
+ <function>checkline_rcsid</function>,
+ <function>checkline_relative_path</function>,
+ <function>checkline_relative_pkgdir</function>,
+ <function>checkline_spellcheck</function> and
+ <function>checkline_cpp_macro_names</function>.</para>
+
+ <para>The rest of the procedures is specific to
+ <filename>Makefile</filename>s:
+ <function>checkline_mk_text</function>,
+ <function>checkline_mk_shellword</function>,
+ <function>checkline_mk_shelltext</function>,
+ <function>checkline_mk_shellcmd</function>,
+ <function>checkline_mk_vartype_basic</function>,
+ <function>checkline_mk_vartype_basic</function>,
+ <function>checkline_mk_vartype</function> and
+ <function>checkline_mk_varassign</function>.</para>
+
+ <para>This class of procedures contains the most code in
+ &pkglint;. The procedures that check shell commands and shell
+ words both have around 200 lines, and the largest procedure is
+ the check for predefined variable types, which has almost 500
+ lines. But the code is not complex at all, since this procedure
+ contains a large switch for all the predefined types. The checks
+ for a single type usually fit on a single screen.</para>
+
+</sect1>
+
+<sect1 id="code.infrastructure">
+<title>The &pkglint; infrastructure</title>
+
+ <para>To keep the code in the checking procedures small and
+ legible, an additional layer of procedures is needed that
+ provides basic operations and abstractions for handling files as
+ a collection of lines and to print all diagnostics in a common
+ format that is suitable to further processing by software
+ tools.</para>
+
+ <para>Since October 2004, this part of &pkglint; makes use of
+ some of the object oriented features of the Perl programming
+ language. It has worked quite well upto now, but it has not been
+ fun to write object-oriented code in Perl. The most basic
+ feature I am missing is that the compiler checks whether an
+ object has a specific method or not, as I have often written
+ <code>$line->warning()</code> instead of
+ <code>$line->log_warning()</code>. This makes refacturing quite
+ difficult if you don't have a 100&nbsp;% coverage test, and I
+ don't have that.</para>
+
+ <para>The classes are all defined in the
+ <varname>PkgLint</varname> namespace.</para>
+
+ <para>The traditional class is <classname>Line</classname>,
+ which represents a logical line of a file. In case of
+ <filename>Makefile</filename>s, line continuations are parsed
+ properly and combined into a single line. For all other files,
+ each logical line corresponds to a physical line. The
+ <classname>Line</classname> class has accessor methods to its
+ fields <methodname>fname</methodname>,
+ <methodname>lines</methodname> and
+ <methodname>text</methodname>. It also has the methods
+ <methodname>log_fatal</methodname>,
+ <methodname>log_error</methodname>,
+ <methodname>log_warning</methodname>,
+ <methodname>log_info</methodname> and
+ <methodname>log_debug</methodname> that all have one parameter,
+ the diagnostics message. The other methods are used less
+ often.</para>
+
+ <para>In January 2006, the logging has been improved in
+ functionality. Before that, a logical line could well consist of
+ 300 physical lines, so a diagnostic would say <quote>you have a
+ bug somewhere between line 100 and 400</quote>. This is not
+ helpful. Therefore, a new class has been invented that allows to
+ map each character of a logical line to its corresponding
+ physical location in the file. The new representation of a
+ logical line is called a <classname>String</classname>. This
+ feature is still experimental, since the only method for logging
+ a string is <methodname>log_warning</methodname>. The others are
+ still missing. It is also completely unclear how lines that have
+ been fixed by &pkglint; are represented since this moves
+ characters around in the physical lines.</para>
+
+ <para>To make pattern matching with the new
+ <classname>String</classname> easy to use, the additional class
+ <classname>StringMatch</classname> has been created. It saves
+ the result of a <classname>String</classname> that is matched
+ against a regular expression. The canonical way to get such a
+ <classname>StringMatch</classname> is to call the
+ <methodname>String::match</methodname> method.</para>
+
+ <para>Since the <classname>StringMatch</classname> was
+ convenient to use, the <classname>SimpleMatch</classname> class
+ represents the result of matching a Perl string against a
+ regular expression. The class <classname>Location</classname> is
+ currently unused.</para>
+
+</sect1>
+<sect1 id="code.style">
+<title>Perl programming style</title>
+
+ <para>The &pkglint; source code has evolved from FreeBSD's portlint,
+ which has been written in Perl, and up to now, &pkglint; is written
+ in Perl. Since one of the main ingredients to &pkglint; are regular
+ expressions, this choice seems natural, and indeed the Perl regular
+ expressions are a great help to keep the code short. But &pkglint;
+ is more than just throwing regular expressions at the package
+ files.</para>
+
+ <para>In 2004, when the &pkglint; source code comprised about
+ 40&nbsp;kilobytes, this was quite appropriate. Since then, the code
+ has become much more structured and various abstraction layers have
+ been inserted. It became more and more clear that the Perl
+ programming language has not been designed with nice-looking source
+ code in mind.</para>
+
+ <para>The first example are subroutines and their parameters. In
+ most other languages, the names of the parameters are mentioned in
+ the subroutine definition. Not so in Perl. The parameters to each
+ subroutine are passed in the <literal>@_</literal> array. The usual
+ way to get named parameters is to write assign the parameter array
+ to a list of local variables. This extra statement is a nuisance,
+ but it is merely syntactical.</para>
+
+ <para>More serious is the way the arguments are passed to a
+ subroutine. Perl allows the programmer to define subroutines with a
+ weak form of prototypes, which helps to catch calls to subroutines
+ that provide a wrong number of arguments. This feature catches many
+ bugs that are easily overlooked. The downside is that anything
+ besides using scalars as parameter types is difficult to understand
+ and quickly leads to unexpected behavior. Therefore the subroutines
+ in &pkglint; only use this style for parameter passing. Oh, and by
+ the way, the subroutine prototypes are only checked for in certain
+ situations like direct calls. In method calls, nothing is checked at
+ all. Since almost all diagnostics are produced by calling
+ <code>$line->log_warning()</code> or
+ <code>$line->log_error()</code>, most of the subroutine calls in
+ &pkglint; go unchecked.</para>
+
+ <para>Instead of using magic numbers, well written code defines
+ named constants for these numbers and then refers to them using
+ their names, giving the reader extra information that plain numbers
+ could not give. Although the constant definitions look quite good in
+ &pkglint; there is one big caveat. The Perl programming language
+ does not know constants. So these definitions are rather shortcuts
+ for defining functions that return the value of the constant. And as
+ functions in Perl have package-wide scope, so have these constants.
+ This is why the namespace prefixes like <varname>SWST_</varname> are
+ necessary to avoid name clashes.</para>
+
+ <para>Most of the constants would be written as an enumeration data
+ type if Perl had one. The same limitation applies for many of the
+ classes (implemented as packages in Perl) that are simply structs.
+ The typical Perl implementation of structs are classes, er, packages
+ which then use methods for accessing the fields. Again, the names of
+ these methods are only checked at runtime, so there is no language
+ support for detecting spelling mistakes in field names.</para>
+
+ <para>Another area where Perl fails to detect many errors is the
+ loose type system. You can apply almost every operator to almost
+ every data type, and the Perl language will give you more or less
+ what you want. Especially it does not prevent you from matching
+ a regular expression against a reference. It will simply compute
+ a string representation of the reference and match the regular
+ expression against that.</para>
+
+ <para>The current Perl interpreter is very inefficient when
+ copying strings. This happens really often in pkglint, for
+ example when passing arguments to functions or saving the result
+ of a regular expression match in <quote>real</quote> variables.
+ For a great speed-up, an implementation that handles string
+ objects by reference-counting them would be better. (Lua comes
+ to mind.)</para>
+
+</sect1>
+<sect1 id="code.lang">
+<title>Switching to another language</title>
+
+ <para>Switching to C++ is not an option, since the typing
+ overhead would be more than twice the current amount. As a
+ consequence the code would become much less readable.</para>
+
+ <para>Switching to OCaml looks nice (because of the type
+ inference), but the regular expressions that are provided by the
+ system are by no means sufficient. On the other hand, since
+ today there is a PCRE package for OCaml in pkgsrc.</para>
+
+</sect1>
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/chap.defs.xml b/pkgtools/pkglint4/files/doc/chap.defs.xml
new file mode 100644
index 00000000000..1bbee6f911a
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.defs.xml
@@ -0,0 +1,26 @@
+<!-- $NetBSD: chap.defs.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+<!-- don't include useless definitions.
+<chapter id="defs">
+<title>Definitions</title>
+
+ <para>In every non-toy program, the need arises to define new
+ words or redefine and clarify existing words. This is the list
+ of words that are used in pkglint.</para>
+
+ <variablelist>
+
+ <varlistentry><term>function</term><listitem><para>A subroutine
+ that is called to obtain a return value, rather than for its
+ side effects. Functions should restrict the user-visible side
+ effects to the necessary minimum.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term>procedure</term><listitem><para>A subroutine
+ that is not called to obtain a return value, but rather called
+ because of its side effects, like input/output.</para>
+ </listitem></varlistentry>
+
+ </variablelist>
+
+</chapter>
+-->
diff --git a/pkgtools/pkglint4/files/doc/chap.design.xml b/pkgtools/pkglint4/files/doc/chap.design.xml
new file mode 100644
index 00000000000..8ee3e98da96
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.design.xml
@@ -0,0 +1,117 @@
+<!-- $NetBSD: chap.design.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="design">
+<title>Design goals</title>
+
+ <para>&pkglint; should be simple to use. It should be consistent
+ and predictable in what it does. The diagnostics should be
+ understandable. The number of false positive and false negative
+ diagnostics should be minimal.</para>
+
+<sect1 id="design.simple-to-use">
+<title>Simple to use</title>
+
+ <para><emphasis>Requirement:</emphasis> Using &pkglint; should
+ not require any knowledge about obscure command line options or
+ hidden features.</para>
+
+ <para>Calling &pkglint; without options gives a useful amount of
+ warnings. No further knowledge is needed. Users that are
+ accustomed to GNU software will quickly find the
+ <literal>--help</literal> command line option, which gives a
+ quite verbose description of the available options. Users that
+ know the GNU compilers will easily remember the
+ <literal>-W</literal> class of options, especially
+ <literal>-Wall</literal>. Other than with the GNU compilers, the
+ latter option enables really <emphasis>all</emphasis> warnings
+ that are intended to be user-visible.</para>
+
+ <para>The command line options come in two flavors: short and
+ long options. The long options are meant to be used when
+ explaining them to others, while the short options are meant to
+ be used when invoking &pkglint; in an interactive shell.</para>
+
+</sect1>
+<sect1 id="design.consistent-and-predictable">
+<title>Consistent and predictable</title>
+
+ <para><emphasis>Requirement:</emphasis> &pkglint; should behave
+ such that the user quickly gets an impression about what
+ &pkglint; does. This impression should be persistent, that is,
+ the output format for diagnostics should be stable over time,
+ and diagnostic messages should not be changed without
+ reason.</para>
+
+ <para>There are only two cases of what the output of
+ &pkglint; is. One is a single line containing the text
+ <quote>looks fine.</quote>, the other is a list of diagnostics,
+ followed by a summary on the number of diagnostics.</para>
+
+ <para>If no warnings are printed, the single line <quote>looks
+ fine.</quote> gives a little motivation to the user. This
+ message is one of the few things that have been kept in
+ &pkglint; since it has been adopted from FreeBSD. It just makes
+ pkglint a more friendly tool. :)</para>
+
+ <para>All error and warning messages are formatted by a single
+ procedure, <function>PkgLint::Logging::log_message</function>. This
+ way, all messages are formatted the same way, which allows easy
+ recognition by human users as well as other tools. There are two
+ different formats available, the traditional one and the gcc-like
+ one. In both formats, each diagnostic occupies exactly one line. Up
+ to the year 2005, some of the longer messages used to take more than
+ one line, but this behavior has been removed.</para>
+
+ <para>The default format is the traditional one. It consists of the
+ severity, in upper-case letters, followed by the filename, the line
+ number and finally the message text. It allows easy recognition of
+ the severity of the messages. Even if errors and warnings are
+ intermixed in the output, the filenames start almost in the same
+ column.</para>
+
+ <para>The gcc-like output format consists of the filename, the line
+ numbers, the severity and finally the message text. It has been
+ added to make it easier to integrate the &pkglint; diagnostics into
+ various text editors, for example Emacs. Since in this format the
+ filename is the first word, it can be easily seen which warning
+ originates in which file.</para>
+
+ <para>There are some other procedures that affect the output, but
+ they have to be enabled explicitly.</para>
+
+</sect1>
+<sect1 id="design.understandable">
+<title>Understandable diagnostics</title>
+
+ <para><emphasis>Requirement:</emphasis> The diagnostics are
+ intended to help the user in writing better package definitions.
+ They should use an unambiguous, clear language and point
+ directly to the problem. If possible, they should include a hint
+ on how the problem can be fixed properly.</para>
+
+</sect1>
+<sect1 id="design.false-diagnostics">
+<title>Few false diagnostics</title>
+
+ <para><emphasis>Requirement:</emphasis> The number of
+ <firstterm>false positives</firstterm>, that is diagnostics
+ where no problem actually exists, should be minimal. On the
+ other hand, &pkglint; should detect as many problems as
+ possible. If it fails to detect a problem, this is called a
+ <firstterm>false negative</firstterm>.</para>
+
+ <para>Currently, there are very few false positives. The way
+ &pkglint; parses the files is already close to the way
+ <command>make</command> and <command>sh</command> parse them, so
+ the structure of the files is modelled quite well.</para>
+
+ <para>Since &pkglint; is also very strict in what it accepts,
+ many problems can already be detected. But since the pkgsrc
+ developers are quite creative when it comes to solving problems,
+ &pkglint; cannot detect everything. After all, the language used
+ to define packages is turing-complete, so it cannot be decided
+ in every case whether a package is valid or not. Luckily, most
+ packages are quite simple.</para>
+
+</sect1>
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/chap.future.xml b/pkgtools/pkglint4/files/doc/chap.future.xml
new file mode 100644
index 00000000000..e76cd43c4f6
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.future.xml
@@ -0,0 +1,91 @@
+<!-- $NetBSD: chap.future.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="future">
+<title>Future directions</title>
+
+<sect1 id="future.tokenize">
+<title>Tokenizing the input</title>
+
+ <para>For providing more exact diagnostics, it would be nice if
+ &pkglint; could point the user to the exact character position
+ of the smallest problematic text in a file. To do this, the
+ file's contents has to be splitted into tokens.</para>
+
+ <para>Doing this is nontrivial, since the tokenizing scheme
+ depends on the context in which the tokens are used. For
+ example, the <varname>COMMENT</varname> variable may contain
+ arbitrary characters (including <literal>'</literal> and
+ <literal>"</literal>), whereas in many other contexts these are
+ parts of quoted shell words.</para>
+
+</sect1>
+<sect1 id="future.ast">
+<title>Working on abstract syntax trees (AST)</title>
+
+ <para>When the tokenizing above is done, the tokens could be
+ parsed by a grammar to form abstract syntax trees. These would
+ consist mainly of function application so that pkglint can infer
+ types and valid values over these trees. The following functions
+ are likely to appear.</para>
+
+ <table id="future.ast.func">
+ <title>Functions in the abstract syntax trees</title>
+ <tgroup cols="2">
+ <thead><row><entry>Function</entry><entry>Purpose</entry></row></thead>
+ <tbody>
+ <row><entry><function>quote</function>(Val)</entry><entry>The <literal>:Q</literal> modifier</entry></row>
+ <row><entry><function>append</function>(Val, Val)</entry><entry>The <literal>+=</literal> operator</entry></row>
+ <row><entry><function>concat</function>(Val, Val)</entry><entry>The direct concatenation of two values</entry></row>
+ <row><entry><function>subst</function>(Val, Subst)</entry><entry>The <literal>:S</literal> and <literal>:C</literal> modifiers</entry></row>
+ <row><entry><function>shell</function>(Val)</entry><entry>The <literal>!=</literal> operator and the <literal>:sh</literal> modifier</entry></row>
+ <row><entry><function>literal</function>(Val)</entry><entry>Introduces literal values</entry></row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>Examples:</para>
+
+<programlisting>
+ WRKSRC= ${WRKDIR}
+ SUBST_SED.pkglint+= -e s\|@DATADIR@\|${PREFIX:Q}/share/pkglint\|g
+</programlisting>
+
+ <para>The first line would be parsed as
+ <literal>assign(var("WRKSRC"), varuse("WRKDIR"))</literal>. The
+ second line would be parsed as
+ <literal>assign(var("SUBST_SED.pkglint"),
+ append(varuse("SUBST_SED.pkglint"), concat(concat(str("-e
+ s\\|@DATADIR@\\|"), quote(varuse("PREFIX"))),
+ str("/share/pkglint\\|g"))))</literal>.</para>
+
+ <para>At this point, unification together with a pattern matcher
+ on tree structures would come in handy, to allow the parser for
+ the shell commands to still operate on this parse tree. This
+ might eventually enable cross-language type inference.</para>
+
+</sect1>
+<sect1 id="future.vars">
+<title>Even more restricted variables</title>
+
+ <para>Currently there are mainly two restrictions for variables:
+ What values they may contain (data types) and where they may be
+ defined and used, on a per-file basis.</para>
+
+ <para>The <filename>makevars.map</filename> file already
+ contains annotations to distinguish user-defined from
+ system-defined variables, but they are currently only used as
+ abbreviations and not further exploited. Based on these
+ definitions, sequence points may be defined in the pkgsrc
+ infrastructure where the values of these variables must have
+ certain properties, like being defined or being fixed (which
+ means that the variable will not change further).</para>
+
+ <para>For example, user-defined variables may then be specified
+ as follows. They are given default values in
+ <filename>mk/defaults/mk.conf</filename>, may be overridden by
+ any file that is included inside <varname>MAKECONF</varname>,
+ and after that, their value is fixed. They may then be used at
+ both load and run time.</para>
+
+</sect1>
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/chap.intro.xml b/pkgtools/pkglint4/files/doc/chap.intro.xml
new file mode 100644
index 00000000000..f9c118eff8c
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.intro.xml
@@ -0,0 +1,15 @@
+<!-- $NetBSD: chap.intro.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="intro">
+<title>Introduction</title>
+
+ <para>&pkglint; is a static analysis tool for pkgsrc packages.
+ It finds many errors and problematic issues in those packages.
+ Starting in June 2004, &pkglint; has evolved into a powerful
+ tool that gives precise warnings wherever possible. With that
+ power comes much additional complexity, which cannot be
+ understood from reading the source code alone. This document
+ provides the necessary background information to understand what
+ the actual code does and why it is done this way.</para>
+
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/chap.statemachines.xml b/pkgtools/pkglint4/files/doc/chap.statemachines.xml
new file mode 100644
index 00000000000..42f5b1792fe
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.statemachines.xml
@@ -0,0 +1,77 @@
+<!-- $NetBSD: chap.statemachines.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="statemachines">
+<title>State machines</title>
+
+ <para>This chapter explains the various state machines that are
+ used in &pkglint;. It also provides graphical representations of
+ them that are much easier to read than the source code.</para>
+
+ <para>The opaque arrows in the figures represent transitions
+ that have a regular expression as condition. The hollow arrows
+ are the default transitions if nothing else matches. When
+ multiple regular expressions match in a state, the one that
+ appears first in the source code is chosen.</para>
+
+<sect1 id="statemachines.shellword">
+<title>The state machine for shell words</title>
+
+ <para>The state machine for single shell words is pretty simple,
+ and I think it can be understood from the source code alone. So
+ no graphical representation is provided.</para>
+
+</sect1>
+
+<sect1 id="statemachines.shellcommand">
+<title>The state machine for shell commands</title>
+
+ <figure id="statemachine.shellcommand">
+ <title>The state transitions for shell commands</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="statemachine.shellcmd.png" format="PNG"/>
+ </imageobject>
+ <textobject><para>(Here should be a drawing of the state transitions.)</para></textobject>
+ </mediaobject>
+ </figure>
+
+ <para>The punch card symbols provide a means to go to a certain
+ state whenever the input matches the text on the punch
+ card.</para>
+
+</sect1>
+
+<sect1 id="statemachines.patch">
+<title>The state machine for patch files</title>
+
+ <para>The state machine for patch files is the newest of the
+ state machines. Here, the state transitions are separated from
+ the code, which makes the code itself pretty small. I don't know
+ yet if this programming style is elegant or not. Time will
+ show.</para>
+
+ <figure id="statemachine.patch">
+ <title>The state transitions for patch files</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="statemachine.patch.png" format="PNG"/>
+ </imageobject>
+ <textobject><para>(Here should be a drawing of the state transitions.)</para></textobject>
+ </mediaobject>
+ </figure>
+
+ <para>The states on the left side are for parsing context diffs,
+ the ones on the right side are for unified diffs. Some of the
+ state names are highly abbreviated as follows. The first letter
+ gives the format of the patch, which is <quote>c</quote> for
+ context diffs and <quote>u</quote> for unified diffs. The second
+ letter gives the current syntactical level, which is
+ <quote>f</quote> for a file header, <quote>h</quote> for a hunk
+ header, or <quote>l</quote> for the hunk lines. The third letter
+ describes the action that belongs to the line, which is
+ <quote>a</quote> for an addition, and <quote>d</quote> for a
+ deletion.</para>
+
+</sect1>
+
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/chap.types.xml b/pkgtools/pkglint4/files/doc/chap.types.xml
new file mode 100644
index 00000000000..77f21c6548f
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/chap.types.xml
@@ -0,0 +1,545 @@
+<!-- $NetBSD: chap.types.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<chapter id="types">
+<title>The &pkglint; type system</title>
+
+ <para>One of the most notable additions to &pkglint; is the
+ introduction of typed variables. Traditionally, in
+ <filename>Makefile</filename>s, all variables have the type
+ <type>String</type>. This prevents many useful checks from being
+ done before executing the code.</para>
+
+ <para>Up to 2004, &pkglint; already did some checks based on
+ the value of the variables, but these checks had no common
+ structure that could be described easily.</para>
+
+<sect1 id="types.history">
+<title>History</title>
+
+ <para>In February 2005, initial support for the &pkglint; type
+ system has been added. Some of the common variables have been
+ assigned types such as <literal><type>Boolean</type></literal>
+ or <literal><type>Yes_Or_Undefined</type></literal>, which are
+ the two common ways to represent boolean variables in pkgsrc.
+ The list of typed variables has been moved from the &pkglint;
+ code to an external file, <filename>makevars.map</filename>.
+ Many more basic types have been added later.</para>
+
+ <para>In October 2005, the type system has been extended to
+ allow <literal><type>List of
+ <replaceable>simple-type</replaceable></type></literal>, which
+ allowed to handle variables like <varname>DEPENDS</varname> and
+ <varname>CFLAGS</varname>. One month later, enumeration types
+ have been added, allowing the type of
+ <varname>PTHREAD_OPTS</varname> to be expressed as <literal>List
+ of { require native }</literal>.</para>
+
+ <para>In May 2006, the definition and use of variables has been
+ further restricted by introducing ACLs, which define the
+ permitted operations (write, append, default, read, preprocess-read)
+ depending on the current file.</para>
+
+</sect1>
+
+<sect1 id="types.syntax">
+<title>Syntax for defining types</title>
+
+<programlisting>
+ type ::= (list-type)? simple-type (acls)?
+
+ list-type ::= ("List" | "InternalList") "of"
+
+ simple-type ::= predefined-type
+ | enumeration
+ predefined-type ::= [A-Za-z][0-9A-Z_a-z]*
+ enumeration ::= "{" (enumeration-item)* "}"
+ enumeration-item ::= [-0-9A-Z_a-z]+
+
+ acls ::= "[" (acl-entry ("," acl-entry)*)? "]"
+ acl-entry ::= acl-subject ":" acl-perms
+ acl-subject ::= [.0-9A-Za-z]+ | "_"
+ acl-perms ::= [adprs]*
+</programlisting>
+
+</sect1>
+<sect1 id="types.semantics">
+<title>Semantics of the types</title>
+
+ <para>The <firstterm>simple types</firstterm> in &pkglint; are
+ either predefined types or enumeration types. A
+ <firstterm>predefined type</firstterm> is used by its name. See
+ <xref linkend="types.predefined"/> for the list of predefined
+ types.</para>
+
+ <para>An expression of an enumeration type may have either of
+ the enumeration-items as a value. It may not reference other
+ variables.</para>
+
+ <para>A list type can be constructed from a predefined type or
+ an enumeration. It is not possible to construct lists of lists,
+ since I have never needed that. There are two types of lists,
+ called <literal>List</literal> and
+ <literal>InternalList</literal>, which are described in the
+ <ulink url="&pkgsrc-guide;/makefile.html">pkgsrc guide, the
+ chapter about <filename>Makefile</filename>s</ulink>.</para>
+
+</sect1>
+<sect1 id="types.acls">
+<title>Access Control Lists</title>
+
+ <para>Additionally to the data type, which specifies
+ <emphasis>what</emphasis> a variable can contain, the ACLs
+ define <emphasis>where</emphasis> the variable can be defined or
+ used (this is called the <firstterm>ACL subject</firstterm>) and
+ which operations are allowed (these are the <firstterm>ACL
+ permissions</firstterm>).</para>
+
+ <para>The ACL subjects are specified by the filename. For
+ example, <filename>Makefile</filename> and
+ <filename>buildlink3.mk</filename> are valid ACL subjects. Since
+ some names occur over an over in pkgsrc, these can be
+ abbreviated as shown in <xref linkend="types.acl.subjects.abbr"
+ />. The character <literal>*</literal> is a placeholder for zero
+ or more arbitrary characters, like in the shell. The possible
+ actions on a variable are shown in <xref
+ linkend="types.acl.perms" />.</para>
+
+ <table id="types.acl.subjects.abbr">
+ <title>ACL Subjects</title>
+ <tgroup cols="2">
+ <thead><row><entry>Subject</entry><entry>Abbreviation</entry></row></thead>
+ <tbody>
+ <row><entry><filename>Makefile</filename></entry><entry>m</entry></row>
+ <row><entry><filename>Makefile.common</filename></entry><entry>c</entry></row>
+ <row><entry><filename>buildlink3.mk</filename></entry><entry>b</entry></row>
+ <row><entry><filename>hacks.mk</filename></entry><entry>h</entry></row>
+ <row><entry><filename>options.mk</filename></entry><entry>o</entry></row>
+ <row><entry>any file</entry><entry>*</entry></row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="types.acl.perms">
+ <title>ACL Permissions</title>
+ <tgroup cols="2">
+ <thead><row><entry>Permission</entry><entry>Description</entry></row></thead>
+ <tbody>
+ <row><entry><filename>a</filename></entry><entry>Append to the
+ variable using the <literal>+=</literal> operator.</entry></row>
+ <row><entry><filename>d</filename></entry><entry>Provide a
+ default value for the variable using the <literal>?=</literal>
+ operator.</entry></row>
+ <row><entry><filename>s</filename></entry><entry>Set the
+ variable unconditionally using the <literal>=</literal>,
+ <literal>:=</literal> or <literal>!=</literal>
+ operator.</entry></row>
+ <row><entry><filename>u</filename></entry><entry>Use the value
+ of the variable.</entry></row>
+ <row><entry><filename>p</filename></entry><entry>Use the value
+ of the variable during preprocessing.</entry></row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>If a variable has no ACL definition at all, all operations
+ are allowed on it. Otherwise exactly those operations of the
+ first ACL entry whose subject matches the current filename are
+ allowed. If no entry matches, nothing is allowed.</para>
+
+ <para>For determining if a variable is used in the correct
+ place, the filename is only one part of the whole decision. The
+ other one is the context in which the variable appears. There
+ are many factors that influence whether the variable is used
+ correctly.</para>
+
+ <itemizedlist>
+
+ <listitem><para>The variable may be either used at preprocessing time
+ or at runtime. Some variables are defined in
+ <filename>bsd.pkg.mk</filename> and thus are not available until
+ that file has been included. As this should always be the very
+ last thing a package includes, it practically means that these
+ variables are only available at runtime.</para></listitem>
+
+ <listitem><para>The variable may appear as the whole right hand side
+ of an assignment, as a single word, or even as part of a word.
+ First, the types on the right and left side should be
+ compatible. Second, some variables need to be quoted correctly,
+ depending on whether they are part of a word or not.</para></listitem>
+
+ <listitem><para>In shell commands, the variable may also appear as a
+ whole word or as part of a word. This is similar to the case
+ above.</para></listitem>
+
+ <listitem><para>The variable may appear inside some sort of quotes.
+ For some variables this is acceptable, as they are assumed to
+ never contain special characters. For others it
+ isn't.</para></listitem>
+
+ <listitem><para>The various operators in conditional statements like
+ <literal>.if</literal> may further restrict the valid values.
+ For example, the <varname>OPSYS</varname> variable should only
+ be compared to well-known operating system names. The
+ <varname>exists()</varname> function should only be called on
+ pathnames. The <varname>defined()</varname> operator only checks
+ if the variable is defined and does not access its
+ value.</para></listitem>
+
+ </itemizedlist>
+
+<sect2 id="types.acls.future">
+<title>Future Directions</title>
+
+ <para>Currently the ACLs only cover the <quote>user
+ space</quote> of pkgsrc. They will be extended later to also
+ check for valid variable definition and use in the pkgsrc
+ infrastructure, as well as the user configuration file. For
+ completeness, those variables that are intended to be specified
+ on the command line will be added to the
+ <filename>makevars.map</filename> file.</para>
+
+</sect2>
+</sect1>
+<sect1 id="types.predefined">
+<title>Predefined types</title>
+
+ <para>There are many predefined types in &pkglint;, which are
+ described below.</para>
+
+ <!-- reference: pkglint.pl, revision 1.532 -->
+ <variablelist>
+
+ <varlistentry><term><literal><type>AwkCommand</type></literal></term>
+ <listitem><para>An awk command. Currently nothing is checked
+ here.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>BuildlinkDepmethod</type></literal></term>
+ <listitem><para>Must be either <literal>build</literal> or
+ <literal>full</literal>.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>BuildlinkDepth</type></literal></term>
+ <listitem><para>This type is only intended for one variable,
+ namely <varname>BUILDLINK_DEPTH</varname>, which is only
+ modified in <filename>buildlink3.mk</filename>
+ files.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>BuildlinkPackages</type></literal></term>
+ <listitem><para>The type of the variable
+ <varname>BUILDLINK_PACKAGES</varname>. Like
+ <literal><type>BuildlinkDepth</type></literal> above, this is
+ only used in <filename>buildlink3.mk</filename> files. This
+ variable has two different patterns to be modified. The first is
+ to remove the current package from itself, and the second is to
+ append the current package. This prevents a package from showing
+ up twice in the list.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Category</type></literal></term>
+ <listitem><para>One of the categories that a package may be
+ placed in. The list of categories has been assembled manually
+ when the type was introduced. There is no further agreement on
+ which valid categories are valid, besides the top level
+ directory names in pkgsrc.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>CFlag</type></literal></term>
+ <listitem><para>One word in a <varname>CFLAGS</varname> or
+ <varname>CPPFLAGS</varname> variable. &pkglint; knows the flags
+ starting with <literal>-D</literal>, <literal>-U</literal>,
+ <literal>-I</literal>. Flags starting with
+ <literal>-O</literal>, <literal>-W</literal>,
+ <literal>-f</literal>, <literal>-g</literal> or
+ <literal>-m</literal> are silently accepted since they are
+ commonly used for the GNU compilers. As the pkgsrc framework
+ does not know how to handle most of these flags, care should be
+ taken.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Comment</type></literal></term>
+ <listitem><para>The comment of a package.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Dependency</type></literal></term>
+ <listitem><para>A simple dependency like
+ <literal>foopkg>=1.0</literal>, <literal>foopkg-[0-9]*</literal>
+ or <literal>foopkg-1.0</literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>DependencyWithPath</type></literal></term>
+ <listitem><para>A dependency (see above), followed by a colon
+ and a relative directory. For some packages, special variables
+ like <varname>USE_TOOLS</varname> should be used instead of an
+ explicit dependency.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>DistSuffix</type></literal></term>
+ <listitem><para>The value of the variable
+ <varname>EXTRACT_SUFX</varname>. The difference in the name is
+ intentional here, since <varname>EXTRACT_SUFX</varname> is a
+ misnomer. <varname>DIST_SUFX</varname> or
+ <varname>DIST_SUFFIX</varname> would be more appropriate.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>EmulPlatform</type></literal></term>
+ <listitem><para>An emulated platform consists of the operating
+ system (in lowercase, as opposed to <type>PlatformTriple</type>)
+ and the hardware architecture.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Filename</type></literal></term>
+ <listitem><para>A filename, as defined in <ulink
+ url="http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_169">POSIX</ulink>.
+ This type further restricts the set of allowed characters.
+ See also <literal><type>Pathname</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Filemask</type></literal></term>
+ <listitem><para>A shell globbing pattern that does not contain a
+ slash. See also <literal><type>Pathmask</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Identifier</type></literal></term>
+ <listitem><para>In various places in pkgsrc, identifiers are
+ used. This type collects the most common naming conventions.
+ When you need a more specific check, you have to write your own
+ check.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>LdFlag</type></literal></term>
+ <listitem><para>A flag that is passed to the linker. Flags
+ starting with <literal>-L</literal> or <literal>-l</literal> are
+ accepted, as well as some others that are assumed to be handled
+ by the wrapper framework.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Mail_Address</type></literal></term>
+ <listitem><para>Checks for a very restricted subset of <ulink
+ url="http://www.ietf.org/rfc/rfc2822.txt">RFC
+ 2822</ulink>.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Message</type></literal></term>
+ <listitem><para>Messages are printed to the user as status
+ indicators. <ulink
+ url="http://www.freebsd.org/cgi/cvsweb.cgi/ports/devel/portlint/src/portlint.pl#rev1.77">As
+ opposed to FreeBSD</ulink>, they should not be quoted since they
+ may be used in contexts where quoting should be done
+ differently.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Option</type></literal></term>
+ <listitem><para>An option from the
+ <literal>PKG_OPTIONS</literal> framework. Options should not
+ contain underscores. They should be documented in
+ <filename>pkgsrc/mk/defaults/options.description</filename>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Pathlist</type></literal></term>
+ <listitem><para>A list of directories that are separated by
+ colons, like the popular environment variable
+ <varname>PATH</varname>. This type differs from the type
+ <literal><type>List of Pathname</type></literal> in the
+ character that is used as a separator.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Pathmask</type></literal></term>
+ <listitem><para>A shell globbing expression that may include
+ slashes.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Pathname</type></literal></term>
+ <listitem><para>A pathname, as defined in <ulink
+ url="http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266">POSIX</ulink>.
+ See also <literal><type>Filename</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Perl5Packlist</type></literal></term>
+ <listitem><para>A common error has been to refer to
+ <varname>INSTALLARCHLIB</varname> in the location of the packing
+ list. Therefore no references to other variables are
+ allowed.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>PkgName</type></literal></term>
+ <listitem><para>A package name should conform to some
+ restrictions, since the filename of the binary package is
+ created from it, which is then interpreted by pkg_add and the
+ like.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>PkgOptionsVar</type></literal></term>
+ <listitem><para>I had once made the mistake of referencing
+ <varname>PKGBASE</varname> in this variable, not knowing that
+ <varname>PKG_OPTIONS_VAR</varname> is used during preprocessing,
+ when <varname>PKGBASE</varname> is not yet defined. This type
+ prevent that mistake from being done again.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>PkgPath</type></literal></term>
+ <listitem><para>A directory name that is relative to the
+ top-level pkgsrc directory. This is only used in specifying
+ specific packages in bulk builds. Despite its name, this type is
+ more similar to <type>RelativePkgDir</type> than to
+ <type>RelativePkgPath</type>.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>PkgRevision</type></literal></term>
+ <listitem><para>The package revision must be a small integer.
+ The only place where this definition may occur is the package
+ <filename>Makefile</filename> itself, as this variable says
+ something about the individual package. There is no mechanism in
+ pkgsrc for something similar to <varname>PKGREVISION</varname>
+ that can be used in <filename>Makefile.common</filename>
+ files.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>PlatformTriple</type></literal></term>
+ <listitem><para>pkgsrc has been ported to many platforms, all of
+ which are identified using a triple of operating system,
+ operating system version and hardware
+ architecture.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Readonly</type></literal></term>
+ <listitem><para>This type is used to mark a variable as being
+ read-only to a package author. As this is not really a data type
+ but an access restriction, it will disappear in the next version
+ of the type system.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>RelativePkgDir</type></literal></term>
+ <listitem><para>A directory name that is relative to the package
+ directory. Mostly used for dependencies. See also
+ <literal><type>RelativePkgPath</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>RelativePkgPath</type></literal></term>
+ <listitem><para>A pathname that is relative to the package
+ directory. It may point to either a regular file or a directory.
+ See also <literal><type>RelativePkgDir</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>ShellCommand</type></literal></term>
+ <listitem><para>A shell command is similar to a
+ <literal><type>List of ShellWord</type></literal>, except that
+ additional checks are performed on the direct use of tool names
+ or certain other deprecated shell commands.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>ShellWord</type></literal></term>
+ <listitem><para>A shell word is what the shell would regard as a
+ single word.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Stage</type></literal></term>
+ <listitem><para>In pkgsrc, there are phases, stages and steps.
+ Especially for the <varname>SUBST_STAGE</varname> variable, this
+ should always be one of the few predefined names, otherwise the
+ whole substitution group will be ignored.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Tool</type></literal></term>
+ <listitem><para>The pkgsrc tools framework contains very few
+ plausibility checks. To prevent spelling mistakes, the list of
+ valid tool names is loaded from the pkgsrc infrastructure files
+ and compared with the names that are used in the
+ <varname>USE_TOOLS</varname> variable.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>URL</type></literal></term>
+ <listitem><para>URLs appear in <varname>MASTER_SITES</varname>
+ and the <varname>HOMEPAGE</varname>. If a
+ <varname>MASTER_SITES</varname> group exists for a given URL, it
+ should be used instead of listing the URL directly.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>UserGroupName</type></literal></term>
+ <listitem><para>User and group names should consist only of
+ alphanumeric characters and the underscore. This restriction
+ ensures maximum portability of pkgsrc.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Userdefined</type></literal></term>
+ <listitem><para>Another instance of misuse of the type system.
+ But it helps to catch some errors in packages. This type will
+ disappear in the next version of the type system. See also
+ <literal><type>Readonly</type></literal>.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Varname</type></literal></term>
+ <listitem><para>Variable names are restricted to only uppercase
+ letters and the underscore in the basename, and arbitrary
+ characters in the parameterized part, following the dot.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>WrkdirSubdirectory</type></literal></term>
+ <listitem><para>The variable <varname>WRKSRC</varname> is
+ usually defined with reference to <varname>WRKDIR</varname>.
+ This check currently does nothing, and I don't know if it's
+ worth to check anything here.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>WrksrcSubdirectory</type></literal></term>
+ <listitem><para>Subdirectories of <varname>WRKSRC</varname> can
+ be used in <varname>CONFIGURE_DIRS</varname> and some other
+ variables. For convenience, they are interpreted relative to
+ <varname>WRKSRC</varname>, so package authors don't have to type
+ <literal>${WRKSRC}</literal> all the time.</para>
+ </listitem></varlistentry>
+
+ <varlistentry><term><literal><type>Yes</type></literal></term>
+ <listitem><para>This type is used for variables that are checked
+ using <literal>defined(VARNAME)</literal>. Their value is
+ interpreted as <quote>true</quote> if they are defined, no
+ matter if they are set to <literal>yes</literal> or
+ <literal>no</literal>.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>YesNo</type></literal></term>
+ <listitem><para>This type is used for variables that are checked
+ using <literal>defined(VARNAME) &amp;&amp;
+ !empty(VARNAME:M[Yy][Ee][Ss])</literal>. A value of
+ <varname>no</varname> means <quote>no</quote> for
+ them.</para></listitem></varlistentry>
+
+ <varlistentry><term><literal><type>YesNoFromCommand</type></literal></term>
+ <listitem><para>Like <literal><type>YesNo</type></literal>, but
+ the value may be produced by a shell command using the
+ <literal>!=</literal> operator.</para></listitem></varlistentry>
+
+ </variablelist>
+
+</sect1>
+<sect1 id="types.future">
+<title>Future directions</title>
+
+<sect2 id="types.kinds">
+<title>Different interpretation of the same data types</title>
+
+ <para>As explained above, there are internal lists and external
+ lists in pkgsrc. But that is not the only attribute that a list
+ can have. They also differ in the way they are defined, which
+ files may access them, and what it means to append to append a
+ value to it.</para>
+
+ <para>For example, <varname>NOT_FOR_PLATFORM</varname> is a list
+ that every file may append to without leading to unexpected
+ behavior. Compare this with
+ <varname>ONLY_FOR_PLATFORM</varname>, which should only be set
+ in a single place throughout pkgsrc. Let's say in the package
+ <filename>Makefile</filename> it is set to
+ <literal>NetBSD-*-*</literal>, because this file's author knows
+ for sure that the package is only usable on NetBSD. Now when
+ some <filename>*.mk</filename> file from a dependency package
+ adds <literal>DragonFly-*-*</literal> to it, the intent of the
+ package <filename>Makefile</filename> is undermined by the
+ dependency package, because now it is possible to build the
+ package on DragonFly, too.</para>
+
+ <para>The same problem arises with the various variables that
+ can be either <literal>yes</literal> or undefined. They should
+ always be chosen so that two definitions in different files
+ don't undermine each other. A good example is
+ <varname>USE_LIBTOOL</varname>, a bad example is
+ <varname>NO_BUILD</varname>.</para>
+
+ <para>TODO: What are the general properties of
+ <quote>good</quote> and <quote>bad</quote> variables? How can it
+ be decided of which kind a certain variable is?</para>
+
+ <para>For most lists, the only valid operation is to append
+ something at the end. Therefore it is good practice to warn if a
+ list is assigned using another operator that
+ <literal>+=</literal>. For <varname>SUBST_CLASSES</varname> this
+ fits perfectly. But for <varname>SUBST_FILES.*</varname> it
+ doesn't. Usually all occurences of a
+ <varname>SUBST_FILES.*</varname> variable occur in the same
+ file, and there should be no other file modifying these
+ variables. Therefore it is better to use the
+ <literal>=</literal> operator for the first of the
+ assignments.</para>
+
+</sect2>
+</sect1>
+</chapter>
diff --git a/pkgtools/pkglint4/files/doc/pkglint.xml b/pkgtools/pkglint4/files/doc/pkglint.xml
new file mode 100644
index 00000000000..d49282e747d
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/pkglint.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"
+[
+ <!ENTITY pkglint "<literal>pkglint</literal>">
+ <!ENTITY pkgsrc-guide "http://www.NetBSD.org/docs/pkgsrc">
+
+ <!ENTITY chap.intro SYSTEM "chap.intro.xml">
+ <!ENTITY chap.defs SYSTEM "chap.defs.xml">
+ <!ENTITY chap.design SYSTEM "chap.design.xml">
+ <!ENTITY chap.types SYSTEM "chap.types.xml">
+ <!ENTITY chap.code SYSTEM "chap.code.xml">
+ <!ENTITY chap.statemachines SYSTEM "chap.statemachines.xml">
+ <!ENTITY chap.future SYSTEM "chap.future.xml">
+]>
+
+<!-- $NetBSD: pkglint.xml,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<book>
+<title>Design and implementation of &pkglint;</title>
+
+<bookinfo>
+<author>
+ <firstname>Roland</firstname>
+ <surname>Illig</surname>
+ <email>rillig@NetBSD.org</email>
+</author>
+</bookinfo>
+
+&chap.intro;
+&chap.defs;
+&chap.design;
+&chap.types;
+&chap.code;
+&chap.statemachines;
+&chap.future;
+
+</book>
diff --git a/pkgtools/pkglint4/files/doc/statemachine.patch.dia b/pkgtools/pkglint4/files/doc/statemachine.patch.dia
new file mode 100644
index 00000000000..bf1aac9919d
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/statemachine.patch.dia
Binary files differ
diff --git a/pkgtools/pkglint4/files/doc/statemachine.shellcmd.dia b/pkgtools/pkglint4/files/doc/statemachine.shellcmd.dia
new file mode 100644
index 00000000000..76b7442f210
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/statemachine.shellcmd.dia
Binary files differ
diff --git a/pkgtools/pkglint4/files/doc/stylesheet.xsl b/pkgtools/pkglint4/files/doc/stylesheet.xsl
new file mode 100644
index 00000000000..078cb37470d
--- /dev/null
+++ b/pkgtools/pkglint4/files/doc/stylesheet.xsl
@@ -0,0 +1,6 @@
+<!-- $NetBSD: stylesheet.xsl,v 1.1 2015/11/25 16:42:21 rillig Exp $ -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+ <xsl:param name="html.longdesc" select="0"/>
+</xsl:stylesheet>
diff --git a/pkgtools/pkglint4/files/makevars.map b/pkgtools/pkglint4/files/makevars.map
new file mode 100644
index 00000000000..d2bd19afcd7
--- /dev/null
+++ b/pkgtools/pkglint4/files/makevars.map
@@ -0,0 +1,768 @@
+# $NetBSD: makevars.map,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+# This file contains the guessed type of some variables, according to
+# their current use in pkgsrc.
+#
+# The type YesNo is used for variables that are checked using
+# .if defined(VAR) && !empty(VAR:M[Yy][Ee][Ss])
+#
+# The type Yes is used for variables that are checked using
+# .if defined(VAR)
+#
+# The type List is used for lists of things. There are two types of lists,
+# InternalList and List, which are described in the pkgsrc guide, chapter
+# "Makefiles".
+#
+# The other types are described in pkglint.pl, checkline_mk_vartype_basic.
+#
+
+#
+# Some commonly used ACLs. For further documentation, see the chapter
+# ``The pkglint type system'' in the pkglint developer documentation.
+#
+
+# A package-defined variable may be set in Makefile, Makefile.common or
+# options.mk, since these are commonly used in pkgsrc. Due to the
+# special nature of buildlink3.mk and builtin.mk files, these files may
+# not define those variables. All other .mk files may, allowing for
+# application.mk or similar helper files.
+acl package = [m:su, c:dsu, b:, builtin.mk:, *.mk:dsu]
+
+# A package_list may be appended to in all "normal" Makefile fragments.
+# This excludes buildlink3.mk and builtin.mk, since they are very
+# special-purpose. Because at the beginning, all lists are empty, in the
+# primary Makefile a direct assignment may be used instead of appending.
+# Since Makefile.common files are usually used by packages that know
+# what they are doing, they may set variables directly, too. This rule
+# can be removed to show a lot of possible problems in pkgsrc.
+acl package_list = [m:asu, c:asu, b:, builtin.mk:, *.mk:au]
+acl bl_list = [b:a, builtin.mk:a]
+
+# A user-defined or system-defined variable must not be set by any
+# package file. It also must not be used in buildlink3.mk and
+# builtin.mk files or at load-time, since the system/user preferences
+# may not have been loaded when these files are included.
+acl user = [b:, builtin.mk:, *:u]
+acl system = [b:, builtin.mk:u, *:u]
+acl cmdline = [b:, builtin.mk:, *:pu]
+
+# The following variables are taken from mk/defaults/mk.conf, 1.118
+
+ALLOW_VULNERABLE_PACKAGES Yes [$user]
+MANINSTALL List of { maninstall catinstall } [$user]
+MANZ Yes [$user]
+GZIP List of ShellWord [$user]
+MKCRYPTO YesNo [$user]
+OBJHOSTNAME Yes [$user]
+OBJMACHINE Yes [$user]
+PKG_SUFX Filename [$user]
+PKGSRC_LOCKTYPE { none sleep once } [$user]
+PKGSRC_SLEEPSECS Integer [$user]
+USETBL Yes [$user]
+ABI { 32 64 } [$user]
+PKG_DEVELOPER Yes [$user]
+USE_ABI_DEPENDS YesNo [$user]
+PKG_REGISTER_SHELLS { YES NO } [$user]
+PKGSRC_COMPILER List of { \
+ ccache ccc clang distcc f2c gcc hp icc \
+ ido gcc mipspro mipspro-ucode pcc \
+ sunpro xlc \
+ } [$user]
+PKGSRC_MESSAGE_RECIPIENTS List of Mail_Address [$user]
+PKGSRC_SHOW_BUILD_DEFS YesNo [$user]
+PKGSRC_SHOW_PATCH_ERRORMSG YesNo [$user]
+PKGSRC_RUN_TEST YesNo [$user]
+PREFER_PKGSRC List of Identifier [$user]
+PREFER_NATIVE List of Identifier [$user]
+PREFER_NATIVE_PTHREADS YesNo [$user]
+LOCALBASE Pathname [$user]
+CROSSBASE Pathname [$user]
+VARBASE Pathname [$user]
+X11_TYPE { modular native } [$user]
+X11BASE Pathname [$user]
+MOTIFBASE Pathname [$user]
+PKGINFODIR Pathname [$user]
+PKGMANDIR Pathname [$user]
+USE_XPKGWEDGE YesNo [$user]
+BSDSRCDIR Pathname [$user]
+BSDXSRCDIR Pathname [$user]
+DISTDIR Pathname [$user]
+DIST_PATH Pathlist [$user]
+DEFAULT_VIEW Unchecked [$user]
+FETCH_CMD ShellCommand [$user]
+FETCH_USING { curl custom fetch ftp manual wget } [$user]
+FETCH_RESUME_ARGS List of ShellWord [$user]
+FETCH_OUTPUT_ARGS List of ShellWord [$user]
+LIBTOOLIZE_PLIST YesNo [$user]
+PKG_RESUME_TRANSFERS YesNo [$user]
+PKG_SYSCONFBASE Pathname [$user]
+RCD_SCRIPTS_DIR Pathname [$user]
+PACKAGES Pathname [$user]
+PKGVULNDIR Pathname [$user]
+PASSIVE_FETCH Yes [$user]
+PATCH_FUZZ_FACTOR { -F0 -F1 -F2 -F3 } [$user]
+ACCEPTABLE_LICENSES List of Identifier [$user]
+SPECIFIC_PKGS Yes [$user]
+SITE_SPECIFIC_PKGS List of PkgPath [$user]
+HOST_SPECIFIC_PKGS List of PkgPath [$user]
+GROUP_SPECIFIC_PKGS List of PkgPath [$user]
+USER_SPECIFIC_PKGS List of PkgPath [$user]
+FAILOVER_FETCH Yes [$user]
+MASTER_SORT List of Unchecked [$user]
+MASTER_SORT_REGEX List of Unchecked [$user]
+PATCH_DEBUG Yes [$user]
+PKG_FC ShellCommand [$user]
+IMAKE ShellCommand [$user]
+IMAKEOPTS List of ShellWord [$user]
+PRE_ROOT_CMD ShellCommand [$user]
+USE_GAMESGROUP YesNo [$package]
+SU_CMD ShellCommand [$user]
+SU_CMD_PATH_APPEND Pathlist [$user]
+FATAL_OBJECT_FMT_SKEW YesNo [$user]
+WARN_NO_OBJECT_FMT YesNo [$user]
+SMART_MESSAGES Yes [$user]
+BINPKG_SITES List of URL [$user]
+BIN_INSTALL_FLAG List of ShellWord [$user]
+LOCALPATCHES Pathname [$user]
+
+# some other variables, sorted alphabetically
+
+.CURDIR Pathname [$system]
+.TARGET Pathname [$system]
+ALL_ENV List of ShellWord
+ALTERNATIVES_FILE Filename
+ALTERNATIVES_SRC List of Pathname
+APACHE_MODULE Yes [$package]
+AR ShellCommand [$system]
+AS ShellCommand [$system]
+AUTOCONF_REQD List of Version [$package_list]
+AUTOMAKE_OVERRIDE List of Pathmask
+AUTOMAKE_REQD List of Version [$package_list]
+AUTO_MKDIRS YesNo [$package]
+BATCH Yes [$user]
+BDB185_DEFAULT Unchecked []
+BDBBASE Pathname [$system]
+BDB_ACCEPTED List of { db1 db2 db3 db4 db5 } [$package]
+BDB_DEFAULT { db1 db2 db3 db4 db5 } []
+BDB_LIBS List of LdFlag [$system]
+BDB_TYPE { db1 db2 db3 db4 db5 } [$system]
+BINGRP UserGroupName [$system]
+BINMODE FileMode [$system]
+BINOWN UserGroupName [$system]
+BOOTSTRAP_DEPENDS InternalList of DependencyWithPath [c:a,m:a,o:a,*.mk:a]
+BOOTSTRAP_PKG YesNo [$package]
+BROKEN Message []
+BROKEN_GETTEXT_DETECTION YesNo [$package]
+BROKEN_EXCEPT_ON_PLATFORM List of PlatformTriple [$package_list]
+BROKEN_ON_PLATFORM InternalList of PlatformTriple [$package_list]
+BSD_MAKE_ENV List of ShellWord [$system]
+BUILDLINK_ABI_DEPENDS.* InternalList of Dependency [*:a]
+BUILDLINK_API_DEPENDS.* InternalList of Dependency [*:a]
+BUILDLINK_CONTENTS_FILTER List of ShellWord []
+# ^^ ShellCommand
+BUILDLINK_CFLAGS List of CFlag [$system]
+BUILDLINK_CFLAGS.* List of CFlag [$bl_list]
+BUILDLINK_CPPFLAGS List of CFlag [$system]
+BUILDLINK_CPPFLAGS.* List of CFlag [$bl_list]
+BUILDLINK_CONTENTS_FILTER.* ShellCommand [b:s]
+BUILDLINK_DEPENDS InternalList of Identifier [b:a]
+BUILDLINK_DEPMETHOD.* List of BuildlinkDepmethod [b:ad,m:as,c:a,*.mk:a]
+# ^^ FIXME: b:d may lead to unexpected behavior.
+BUILDLINK_DEPTH BuildlinkDepth [b:ps, builtin.mk:ps]
+BUILDLINK_DIR Pathname [$system]
+BUILDLINK_FILES.* List of Pathmask [$bl_list]
+BUILDLINK_FILES_CMD.* List of ShellWord []
+# ^^ ShellCommand
+BUILDLINK_INCDIRS.* List of Pathname [b:ad] # b:d?
+BUILDLINK_JAVA_PREFIX.* Pathname [b:s]
+BUILDLINK_LDADD.* List of LdFlag [builtin.mk:adsu, b:, m:u, c:u, *.mk:u]
+BUILDLINK_LDFLAGS List of LdFlag [$system]
+BUILDLINK_LDFLAGS.* List of LdFlag [$bl_list]
+BUILDLINK_LIBDIRS.* List of Pathname [$bl_list]
+BUILDLINK_LIBS.* List of LdFlag [b:a]
+BUILDLINK_PACKAGES BuildlinkPackages [b:aps]
+BUILDLINK_PASSTHRU_DIRS List of Pathname [m:a,c:a,b:a,h:a]
+BUILDLINK_PASSTHRU_RPATHDIRS List of Pathname [m:a,c:a,b:a,h:a]
+BUILDLINK_PKGSRCDIR.* RelativePkgDir [b:dp]
+BUILDLINK_PREFIX.* Pathname [builtin.mk:su, b:, m:u, c:u, *.mk:u]
+BUILDLINK_RPATHDIRS.* List of Pathname [b:a]
+BUILDLINK_TARGETS List of Identifier []
+BUILDLINK_FNAME_TRANSFORM.* SedCommands [m:a,builtin.mk:a,h:a,b:a]
+BUILDLINK_TRANSFORM List of WrapperTransform [*:a]
+BUILDLINK_TREE List of Identifier [b:a]
+BUILD_DEFS List of Varname [m:a,c:a,o:a]
+BUILD_DEPENDS InternalList of DependencyWithPath [c:a,m:a,o:a,*.mk:a]
+BUILD_DIRS List of WrksrcSubdirectory [$package_list]
+BUILD_ENV List of ShellWord [$package_list]
+BUILD_MAKE_CMD ShellCommand [$system]
+BUILD_MAKE_FLAGS List of ShellWord [$package_list]
+BUILD_TARGET List of Identifier [$package]
+BUILD_USES_MSGFMT Yes [$package]
+BUILTIN_PKG Identifier [builtin.mk:psu]
+BUILTIN_PKG.* PkgName [builtin.mk:psu]
+BUILTIN_FIND_FILES_VAR List of Varname [builtin.mk:s]
+BUILTIN_FIND_FILES.* List of Pathname [builtin.mk:s]
+BUILTIN_FIND_GREP.* String [builtin.mk:s]
+BUILTIN_FIND_LIBS List of Pathname [builtin.mk:s]
+BUILTIN_IMAKE_CHECK List of Unchecked [builtin.mk:s]
+BUILTIN_IMAKE_CHECK.* YesNo []
+BUILTIN_X11_TYPE Unchecked [$system]
+BUILTIN_X11_VERSION Unchecked [$system]
+CATEGORIES List of Category [m:as,c:ads]
+CC_VERSION Message [$system]
+CC ShellCommand [$system]
+CFLAGS* List of CFlag [$package_list]
+# ^^ may also be changed by the user
+CHECK_BUILTIN YesNo [builtin.mk:d,m:s]
+CHECK_BUILTIN.* YesNo [*:p]
+CHECK_FILES_SKIP List of Pathmask [m:a,c:a]
+CHECK_FILES_SUPPORTED YesNo [$package]
+CHECK_HEADERS YesNo [$user]
+CHECK_HEADERS_SKIP List of Pathmask [$package_list]
+CHECK_INTERPRETER YesNo [$user]
+CHECK_INTERPRETER_SKIP List of Pathmask [$package_list]
+CHECK_PERMS YesNo [$user]
+CHECK_PERMS_SKIP List of Pathmask [$package_list]
+#CHECK_PERMS_AUTOFIX YesNo [$package]
+# ^^ experimental
+CHECK_PORTABILITY YesNo [$user]
+CHECK_PORTABILITY_SKIP List of Pathmask [$package_list]
+CHECK_SHLIBS YesNo [m:s]
+CHECK_SHLIBS_SKIP List of Pathmask [$package_list]
+CHECK_SHLIBS_SUPPORTED YesNo [m:s]
+CHECK_WRKREF_SKIP List of Pathmask [$package_list]
+CMAKE_ARG_PATH Pathname [$package]
+CMAKE_ARGS List of ShellWord [$package_list]
+COMMENT Comment [m:as,c:as]
+COMPILER_RPATH_FLAG { -Wl,-rpath } [$system]
+CONFIGURE_ARGS List of ShellWord [$package_list]
+CONFIGURE_DIRS List of WrksrcSubdirectory [$package_list]
+CONFIGURE_ENV List of ShellWord [$package_list]
+CONFIGURE_HAS_INFODIR YesNo [$package]
+CONFIGURE_HAS_LIBDIR YesNo [$package]
+CONFIGURE_HAS_MANDIR YesNo [$package]
+CONFIGURE_SCRIPT Pathname [$package]
+CONFIG_GUESS_OVERRIDE List of Pathmask [m:as,c:as]
+CONFIG_STATUS_OVERRIDE List of Pathmask [m:as,c:as]
+CONFIG_SHELL Pathname [m:s,c:s]
+CONFIG_SUB_OVERRIDE List of Pathmask [m:as,c:as]
+CONFLICTS InternalList of Dependency [$package_list]
+CONF_FILES List of ShellWord [$package_list]
+CONF_FILES_MODE { 0644 0640 0600 0400 } [$package]
+CONF_FILES_PERMS List of ShellWord [$package_list]
+COPY { -c } [$system]
+# ^^ the flag that tells ${INSTALL} to copy a file
+CPP ShellCommand [$system]
+CPPFLAGS* List of CFlag [$package_list]
+CRYPTO Yes [m:s]
+CXX ShellCommand [$system]
+CXXFLAGS* List of CFlag [$package_list]
+DEINSTALL_FILE Pathname [m:s]
+DEINSTALL_SRC List of Pathname [m:s,c:ds]
+DEINSTALL_TEMPLATES List of Pathname [m:as,c:ads]
+DELAYED_ERROR_MSG ShellCommand [$system]
+DELAYED_WARNING_MSG ShellCommand [$system]
+DEPENDS InternalList of DependencyWithPath [$package_list]
+DEPENDS_TARGET List of Identifier [$user]
+DESCR_SRC List of Pathname [m:s,c:ds]
+DESTDIR Pathname [$system]
+DESTDIR_VARNAME Varname [m:s,c:s]
+DEVOSSAUDIO Pathname [$system]
+DEVOSSSOUND Pathname [$system]
+DISTFILES List of Filename [$package_list]
+DISTINFO_FILE RelativePkgPath [$package]
+DISTNAME Filename [$package]
+DIST_SUBDIR Pathname [$package]
+DJB_BUILD_ARGS List of ShellWord
+DJB_BUILD_TARGETS List of Identifier
+DJB_CONFIG_CMDS List of ShellWord [o:s]
+# ^^ ShellCommand, terminated by a semicolon
+DJB_CONFIG_DIRS List of WrksrcSubdirectory
+DJB_CONFIG_HOME Filename
+DJB_CONFIG_PREFIX Pathname
+DJB_INSTALL_TARGETS List of Identifier
+DJB_MAKE_TARGETS YesNo
+DJB_RESTRICTED YesNo [m:s]
+DJB_SLASHPACKAGE YesNo
+DLOPEN_REQUIRE_PTHREADS YesNo
+DL_AUTO_VARS Yes [m:s,c:s,o:s]
+DL_LIBS List of LdFlag
+DOCOWN UserGroupName [$system]
+DOCGRP UserGroupName [$system]
+DOCMODE FileMode [$system]
+DOWNLOADED_DISTFILE Pathname [$system]
+DO_NADA ShellCommand [$system]
+DYNAMIC_SITES_CMD ShellCommand [$package]
+DYNAMIC_SITES_SCRIPT Pathname [$package]
+ECHO ShellCommand [$system]
+ECHO_MSG ShellCommand [$system]
+ECHO_N ShellCommand [$system]
+EGDIR Pathname [$package]
+# ^^ This variable is not defined by the system, but has been established
+# as a convention.
+EMACS_BIN Pathname [$system]
+EMACS_ETCPREFIX Pathname [$system]
+EMACS_FLAVOR { emacs xemacs } [$system]
+EMACS_INFOPREFIX Pathname [$system]
+EMACS_LISPPREFIX Pathname [$system]
+EMACS_MODULES List of Identifier [m:as,c:as]
+EMACS_PKGNAME_PREFIX Identifier [$system]
+# ^^ or the empty string.
+EMACS_TYPE { emacs xemacs } [$system]
+EMACS_USE_LEIM Yes
+EMACS_VERSIONS_ACCEPTED List of { emacs25 emacs24 emacs24nox emacs23 emacs23nox emacs22 emacs22nox emacs21 emacs21nox emacs20 xemacs215 xemacs215nox xemacs214 xemacs214nox} [m:s]
+EMACS_VERSION_MAJOR Integer [$system]
+EMACS_VERSION_MINOR Integer [$system]
+EMACS_VERSION_REQD List of { emacs24 emacs24nox emacs23 emacs23nox emacs22 emacs22nox emacs21 emacs21nox emacs20 xemacs215 xemacs214 } [m:as]
+EMULDIR Pathname [$system]
+EMULSUBDIR Pathname [$system]
+OPSYS_EMULDIR Pathname [$system]
+EMULSUBDIRSLASH Pathname [$system]
+EMUL_ARCH { i386 none } [$system]
+EMUL_DISTRO Identifier [$system]
+EMUL_IS_NATIVE Yes [$system]
+EMUL_MODULES.* List of Identifier [$package]
+EMUL_OPSYS { freebsd hpux irix linux osf1 solaris sunos none } [$system]
+EMUL_PKG_FMT { plain rpm } [$package]
+EMUL_PLATFORM EmulPlatform [$user]
+EMUL_PLATFORMS List of EmulPlatform [$package]
+EMUL_PREFER List of EmulPlatform [$user]
+EMUL_REQD InternalList of Dependency [$package]
+EMUL_TYPE.* { native builtin suse suse-9.1 suse-9.x suse-10.0 suse-10.x } [$user]
+ERROR_CAT ShellCommand [$system]
+ERROR_MSG ShellCommand [$system]
+EVAL_PREFIX InternalList of ShellWord [m:a,c:a]
+# ^^ FIXME: Looks like a type mismatch.
+EXPORT_SYMBOLS_LDFLAGS List of LdFlag [$system]
+EXTRACT_CMD ShellCommand [$system]
+EXTRACT_DIR Pathname [$package]
+EXTRACT_ELEMENTS List of Pathmask [$package_list]
+EXTRACT_ENV List of ShellWord [$package_list]
+EXTRACT_ONLY List of Pathname [$package_list]
+EXTRACT_OPTS List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_BIN List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_LHA List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_PAX List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_RAR List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_TAR List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_ZIP List of ShellWord [m:as,c:as]
+EXTRACT_OPTS_ZOO List of ShellWord [m:as,c:as]
+EXTRACT_SUFX DistSuffix [$package]
+EXTRACT_USING { bsdtar gtar nbtar pax } [$package]
+FAIL_MSG ShellCommand [$system]
+FAMBASE Pathname [$system]
+FAM_ACCEPTED List of { fam gamin } [$package]
+FAM_DEFAULT { fam gamin } [$user]
+FAM_TYPE { fam gamin } [$system]
+FETCH_BEFORE_ARGS List of ShellWord [m:as]
+FETCH_MESSAGE List of ShellWord [$package_list]
+FILESDIR RelativePkgPath [$package]
+FILES_SUBST List of ShellWord [$package_list]
+FILES_SUBST_SED List of ShellWord
+FIX_RPATH List of Varname [$package_list]
+FLEX_REQD List of Version [$package_list]
+FONTS_DIRS.* List of Pathname [m:as,c:a]
+GAMEDATAMODE FileMode [$system]
+GAMES_GROUP UserGroupName [$system]
+GAMEMODE FileMode [$system]
+GAMES_USER UserGroupName [$system]
+GCC_REQD List of Version [$package_list]
+GENERATE_PLIST List of ShellWord [$package_list]
+# ^^ List of Shellcommand, terminated with a semicolon
+GITHUB_PROJECT Identifier [$package]
+GITHUB_TAG Identifier [$package]
+GITHUB_RELEASE Filename [$package]
+GITHUB_TYPE { tag release } [$package]
+GNU_ARCH { mips }
+GNU_CONFIGURE Yes [c:s,m:s]
+GNU_CONFIGURE_INFODIR Pathname [m:s,c:s]
+GNU_CONFIGURE_LIBDIR Pathname [m:s,c:s]
+GNU_CONFIGURE_LIBSUBDIR Pathname [$package]
+GNU_CONFIGURE_MANDIR Pathname [m:s,c:s]
+GNU_CONFIGURE_PREFIX Pathname [m:s]
+HAS_CONFIGURE Yes [m:s,c:s]
+HEADER_TEMPLATES List of Pathname [$package_list]
+HOMEPAGE URL [$package]
+IGNORE_PKG.* Yes [*:sp]
+INCOMPAT_CURSES InternalList of PlatformTriple [m:as]
+INCOMPAT_ICONV InternalList of PlatformTriple
+INFO_DIR Pathname
+# ^^ relative to PREFIX
+INFO_FILES Yes [$package]
+INSTALL ShellCommand [$system]
+INSTALLATION_DIRS List of PrefixPathname [$package_list]
+INSTALLATION_DIRS_FROM_PLIST Yes [$package]
+INSTALL_DATA ShellCommand [$system]
+INSTALL_DATA_DIR ShellCommand [$system]
+INSTALL_DIRS List of WrksrcSubdirectory [$package_list]
+INSTALL_ENV List of ShellWord [$package_list]
+INSTALL_FILE Pathname [m:s]
+INSTALL_GAME ShellCommand [$system]
+INSTALL_GAME_DATA ShellCommand [$system]
+INSTALL_LIB ShellCommand [$system]
+INSTALL_LIB_DIR ShellCommand [$system]
+INSTALL_MAKE_FLAGS List of ShellWord [$package_list]
+INSTALL_MAN ShellCommand [$system]
+INSTALL_MAN_DIR ShellCommand [$system]
+INSTALL_PROGRAM ShellCommand [$system]
+INSTALL_PROGRAM_DIR ShellCommand [$system]
+INSTALL_SCRIPT ShellCommand [$system]
+INSTALL_SCRIPTS_ENV List of ShellWord
+INSTALL_SCRIPT_DIR ShellCommand [$system]
+INSTALL_SRC List of Pathname [m:s,c:ds]
+INSTALL_TARGET List of Identifier [$package]
+INSTALL_TEMPLATES List of Pathname [m:as,c:ads]
+INSTALL_UNSTRIPPED YesNo [m:s,c:s]
+INTERACTIVE_STAGE List of { fetch extract configure build install } [$package]
+IS_BUILTIN.* YesNo_Indirectly [builtin.mk:psu]
+JAVA_BINPREFIX Pathname [$system]
+JAVA_CLASSPATH ShellWord [$package]
+JAVA_HOME Pathname [$package]
+JAVA_NAME Filename [$package]
+JAVA_UNLIMIT List of { cmdsize datasize stacksize } [$package_list]
+JAVA_WRAPPERS InternalList of Filename [$package_list]
+JAVA_WRAPPER_BIN.* Pathname [$package]
+KRB5BASE Pathname [$system]
+KRB5_ACCEPTED List of { heimdal mit-krb5 }
+KRB5_DEFAULT { heimdal mit-krb5 } [$user]
+KRB5_TYPE Unchecked [$system]
+LD ShellCommand [$system]
+LDFLAGS* List of LdFlag [$package_list]
+LIBGRP UserGroupName [$system]
+LIBMODE FileMode [$system]
+LIBOWN UserGroupName [$system]
+LIBOSSAUDIO Pathname [$system]
+LIBS* List of LdFlag [$package_list]
+LIBTOOL ShellCommand [$system]
+LIBTOOL_OVERRIDE List of Pathmask [m:as]
+LIBTOOL_REQD List of Version [$package_list]
+LICENCE License [m:s,c:s,o:s]
+LICENSE License [m:s,c:s,o:s]
+LICENSE_FILE Pathname [$package]
+LINKER_RPATH_FLAG ShellWord [$system]
+LOWER_OPSYS Identifier [$system]
+LTCONFIG_OVERRIDE List of Pathmask [m:as,c:a]
+MACHINE_ARCH Identifier [$system]
+MACHINE_GNU_PLATFORM PlatformTriple [$system]
+MAINTAINER Mail_Address [m:s,c:d]
+MAKE ShellCommand [$system]
+MAKEFLAGS List of ShellWord [$package_list]
+MAKEVARS List of Varname [builtin.mk:a,b:a,h:a]
+MAKE_DIRS List of Pathname [$package_list]
+MAKE_DIRS_PERMS List of ShellWord [$package_list]
+MAKE_ENV List of ShellWord [$package_list]
+MAKE_FILE Pathname [$package]
+MAKE_FLAGS List of ShellWord [$package_list]
+MAKE_JOBS Integer [$user]
+MAKE_JOBS_SAFE YesNo [$package]
+MAKE_PROGRAM ShellCommand [$package]
+MANCOMPRESSED YesNo [m:s,c:ds]
+MANCOMPRESSED_IF_MANZ Yes [m:s,c:ds]
+MANGRP UserGroupName [$system]
+MANMODE FileMode [$system]
+MANOWN UserGroupName [$system]
+MASTER_SITES List of FetchURL [$package_list]
+MASTER_SITE_APACHE List of FetchURL [$system]
+MASTER_SITE_BACKUP List of FetchURL [$system]
+MASTER_SITE_CYGWIN List of FetchURL [$system]
+MASTER_SITE_DEBIAN List of FetchURL [$system]
+MASTER_SITE_FREEBSD List of FetchURL [$system]
+MASTER_SITE_FREEBSD_LOCAL List of FetchURL [$system]
+MASTER_SITE_GENTOO List of FetchURL [$system]
+MASTER_SITE_GITHUB List of FetchURL [$system]
+MASTER_SITE_GNOME List of FetchURL [$system]
+MASTER_SITE_GNU List of FetchURL [$system]
+MASTER_SITE_GNUSTEP List of FetchURL [$system]
+MASTER_SITE_IFARCHIVE List of FetchURL [$system]
+MASTER_SITE_HASKELL_HACKAGE List of FetchURL [$system]
+MASTER_SITE_KDE List of FetchURL [$system]
+MASTER_SITE_LOCAL List of FetchURL [$system]
+MASTER_SITE_MOZILLA List of FetchURL [$system]
+MASTER_SITE_MOZILLA_ALL List of FetchURL [$system]
+MASTER_SITE_MOZILLA_ESR List of FetchURL [$system]
+MASTER_SITE_MYSQL List of FetchURL [$system]
+MASTER_SITE_NETLIB List of FetchURL [$system]
+MASTER_SITE_OPENOFFICE List of FetchURL [$system]
+MASTER_SITE_OSDN List of FetchURL [$system]
+MASTER_SITE_PERL_CPAN List of FetchURL [$system]
+MASTER_SITE_R_CRAN List of FetchURL [$system]
+MASTER_SITE_RUBYGEMS List of FetchURL [$system]
+MASTER_SITE_SOURCEFORGE List of FetchURL [$system]
+MASTER_SITE_SUNSITE List of FetchURL [$system]
+MASTER_SITE_SUSE List of FetchURL [$system]
+MASTER_SITE_TEX_CTAN List of FetchURL [$system]
+MASTER_SITE_XCONTRIB List of FetchURL [$system]
+MASTER_SITE_XEMACS List of FetchURL [$system]
+MESSAGE_SRC List of Pathname [$package_list]
+MESSAGE_SUBST List of ShellWord [c:a,m:a,o:a]
+META_PACKAGE Yes [$package]
+MISSING_FEATURES List of Identifier [$system]
+MYSQL_VERSIONS_ACCEPTED List of { 51 55 56 } [m:s]
+MYSQL_VERSION_DEFAULT Version [$user]
+NM ShellCommand [$system]
+NONBINMODE FileMode [$system]
+NOT_FOR_COMPILER List of { ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc } [$package]
+NOT_FOR_PLATFORM InternalList of PlatformTriple [$package_list]
+NOT_FOR_UNPRIVILEGED YesNo [$package]
+NO_BIN_ON_CDROM Restricted [m:s,c:s]
+NO_BIN_ON_FTP Restricted [m:s,c:s]
+NO_BUILD Yes [m:s,c:s,Makefile.*:ds]
+NO_CHECKSUM Yes [$package]
+NO_CONFIGURE Yes [$package]
+NO_EXPORT_CPP Yes [m:s]
+NO_EXTRACT Yes [$package]
+NO_INSTALL_MANPAGES Yes [$package]
+# ^^ only has an effect for Imake packages.
+NO_PKGTOOLS_REQD_CHECK Yes [m:s]
+NO_SRC_ON_CDROM Restricted [m:s,c:s]
+NO_SRC_ON_FTP Restricted [m:s,c:s]
+ONLY_FOR_COMPILER List of { ccc clang gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc } [$package_list]
+ONLY_FOR_PLATFORM InternalList of PlatformTriple [$package_list]
+ONLY_FOR_UNPRIVILEGED YesNo [$package]
+OPSYS Identifier [$system]
+OPSYSVARS List of Varname [m:a,c:a]
+OSVERSION_SPECIFIC Yes [m:s,c:s]
+OS_VERSION Version [$system]
+OVERRIDE_DIRDEPTH* Integer [$package]
+OVERRIDE_GNU_CONFIG_SCRIPTS Yes [$package]
+OWNER Mail_Address [m:s,c:d]
+OWN_DIRS List of Pathname [$package_list]
+OWN_DIRS_PERMS List of ShellWord [$package_list]
+PAMBASE Pathname [$system]
+PAM_DEFAULT { linux-pam openpam solaris-pam } [$user]
+PATCHDIR RelativePkgPath [m:s,c:ds]
+PATCHFILES List of Filename [$package_list]
+PATCH_ARGS List of ShellWord
+PATCH_DIST_ARGS List of ShellWord [m:as]
+PATCH_DIST_CAT ShellCommand
+PATCH_DIST_STRIP* ShellWord [m:s, c:s, b:, builtin.mk:, *.mk:s]
+PATCH_SITES List of URL [m:s,o:s,c:s]
+PATCH_STRIP ShellWord
+PERL5_USE_PACKLIST YesNo [$package]
+PERL5_PACKLIST List of Perl5Packlist [m:s,o:sa]
+PERL5_PACKLIST_DIR Pathname []
+PGSQL_PREFIX Pathname [$system]
+PGSQL_VERSIONS_ACCEPTED List of { 91 92 93 94}
+PGSQL_VERSION_DEFAULT Version [$user]
+PG_LIB_EXT { dylib so } [$system]
+PGSQL_TYPE { postgresql81-client postgresql80-client } [$system]
+PGPKGSRCDIR Pathname [$system]
+PHASE_MSG ShellCommand [$system]
+PHP_VERSION_REQD Version [$user]
+PKGBASE Identifier [$system]
+PKGCONFIG_OVERRIDE List of Pathmask [m:as,c:a]
+PKGCONFIG_OVERRIDE_STAGE Stage [$package]
+PKGDIR RelativePkgDir [$package]
+PKGDIRMODE FileMode [$system]
+PKGLOCALEDIR Pathname [$system]
+PKGNAME PkgName [$package]
+PKGNAME_NOREV PkgName [$system]
+PKGPATH Pathname [$system]
+PKGREPOSITORY Unchecked []
+PKGREVISION PkgRevision [m:s]
+PKGSRCDIR Pathname [$system]
+PKGSRCTOP Yes [m:s]
+PKGTOOLS_ENV List of ShellWord
+PKGVERSION Version [$system]
+PKGWILDCARD Filemask [$system]
+PKG_ADMIN ShellCommand [$system]
+PKG_APACHE { apache22 apache24 } [$system]
+PKG_APACHE_ACCEPTED List of { apache22 apache24 } [$package]
+PKG_APACHE_DEFAULT { apache22 apache24 } [$user]
+PKG_CONFIG Yes [$user]
+# ^^ No, this is not the popular command from GNOME, but the setting
+# whether the pkgsrc user wants configuration files automatically
+# installed or not.
+PKG_CREATE ShellCommand [$system]
+PKG_DBDIR Pathname [$system]
+PKG_DEBUG_LEVEL Integer [$cmdline]
+PKG_DEFAULT_OPTIONS List of Option [$user]
+PKG_DELETE ShellCommand [$system]
+PKG_DESTDIR_SUPPORT List of { destdir user-destdir } [m:s,c:s]
+PKG_FAIL_REASON List of ShellWord [$package_list]
+PKG_GECOS.* Message [m:s]
+PKG_GID.* Integer [m:s]
+PKG_GROUPS List of ShellWord [m:as]
+PKG_GROUPS_VARS List of Varname [$package_list]
+PKG_HOME.* Pathname [m:s]
+PKG_HACKS List of Identifier [h:a]
+PKG_INFO ShellCommand [$system]
+PKG_JAVA_HOME Pathname [$system]
+PKG_JVM { \
+ blackdown-jdk13 \
+ jdk jdk14 \
+ kaffe \
+ sun-jdk13 sun-jdk14 sun-jdk15 sun-jdk6 \
+ openjdk7 openjdk7-bin sun-jdk7 \
+ } [$system]
+PKG_JVMS_ACCEPTED List of { \
+ blackdown-jdk13 \
+ jdk jdk14 \
+ kaffe \
+ sun-jdk13 sun-jdk14 sun-jdk15 sun-jdk6 \
+ openjdk7 openjdk7-bin sun-jdk7 \
+ } [m:s,c:ds]
+PKG_JVM_DEFAULT { \
+ blackdown-jdk13 \
+ jdk jdk14 \
+ kaffe \
+ sun-jdk13 sun-jdk14 sun-jdk15 sun-jdk6 \
+ openjdk7 openjdk7-bin sun-jdk7 \
+ } [$user]
+PKG_LEGACY_OPTIONS List of Option
+PKG_LIBTOOL Pathname [m:s]
+PKG_OPTIONS InternalList of Option [bsd.options.mk:s,*:pu]
+PKG_OPTIONS.* InternalList of Option [$user]
+PKG_OPTIONS_DEPRECATED_WARNINGS List of ShellWord
+PKG_OPTIONS_GROUP.* InternalList of Option [o:s,m:s]
+PKG_OPTIONS_LEGACY_OPTS InternalList of Unchecked [m:a,c:a,o:a]
+PKG_OPTIONS_LEGACY_VARS InternalList of Unchecked [m:a,c:a,o:a]
+PKG_OPTIONS_NONEMPTY_SETS InternalList of Identifier
+PKG_OPTIONS_OPTIONAL_GROUPS InternalList of Identifier [o:as]
+PKG_OPTIONS_REQUIRED_GROUPS InternalList of Identifier [o:s,m:s]
+PKG_OPTIONS_SET.* InternalList of Option
+PKG_OPTIONS_VAR PkgOptionsVar [o:s,m:s,c:s, bsd.options.mk:p]
+PKG_PRESERVE Yes [m:s]
+PKG_SHELL Pathname [m:s,c:s]
+PKG_SHELL.* Pathname [m:s,c:s]
+PKG_SHLIBTOOL Pathname
+PKG_SKIP_REASON List of ShellWord [$package_list]
+PKG_SUGGESTED_OPTIONS List of Option [o:as,m:as,c:s]
+PKG_SUPPORTED_OPTIONS List of Option [o:as,m:as,c:s]
+PKG_SYSCONFDIR* Pathname [$package]
+PKG_SYSCONFDIR_PERMS List of ShellWord [$package_list]
+PKG_SYSCONFBASEDIR Pathname [$system]
+PKG_SYSCONFSUBDIR Pathname [$package]
+PKG_SYSCONFVAR Identifier
+# ^^ FIXME: name/type mismatch.
+PKG_UID Integer [m:s]
+PKG_USERS List of ShellWord [m:as]
+PKG_USERS_VARS List of Varname [$package]
+PKG_USE_KERBEROS Yes [m:s,c:s]
+#PLIST.* # has special handling code
+PLIST_VARS List of Identifier [$package_list]
+PLIST_SRC List of RelativePkgPath [$package_list]
+PLIST_SUBST List of ShellWord [$package_list]
+PLIST_TYPE { dynamic static }
+PREPEND_PATH List of Pathname
+PREFIX Pathname [*:u] # ???
+PREV_PKGPATH Pathname [*:u] # doesn't exist any longer
+PRINT_PLIST_AWK AwkCommand [*:a]
+PRIVILEGED_STAGES List of { install package clean }
+PTHREAD_AUTO_VARS YesNo [m:s]
+PTHREAD_CFLAGS List of CFlag [$system]
+PTHREAD_LDFLAGS List of LdFlag [$system]
+PTHREAD_LIBS List of LdFlag [$system]
+PTHREAD_OPTS List of { native optional require } [m:as,c:a,b:a]
+PTHREAD_TYPE Identifier [$system]
+# ^^ or "native" or "none".
+PY_PATCHPLIST Yes [$package]
+PYPKGPREFIX { py27 py33 py34 } [*:pu, pyversion.mk:s, *:]
+PYTHON_FOR_BUILD_ONLY Yes [$package]
+REPLACE_PYTHON List of Pathmask [$package_list]
+PYTHON_VERSIONS_ACCEPTED List of Version [$package]
+PYTHON_VERSIONS_INCOMPATIBLE List of Version [$package]
+PYTHON_VERSION_DEFAULT Version [$user]
+PYTHON_VERSION_REQD Version [$user]
+PYTHON_VERSIONED_DEPENDENCIES List of PythonDependency [$package_list]
+RANLIB ShellCommand [$system]
+RCD_SCRIPTS List of Filename [$package_list]
+RCD_SCRIPT_SRC.* List of Pathname [m:s]
+REPLACE.* String [m:s]
+REPLACE_AWK List of Pathmask [$package_list]
+REPLACE_BASH List of Pathmask [$package_list]
+REPLACE_CSH List of Pathmask [$package_list]
+REPLACE_EMACS List of Pathmask
+REPLACE_FILES.* List of Pathmask [m:as,c:as]
+REPLACE_INTERPRETER List of Identifier [m:a,c:a]
+REPLACE_KSH List of Pathmask [$package_list]
+REPLACE_LOCALEDIR_PATTERNS List of Filemask [$package_list]
+REPLACE_LUA List of Pathmask [$package_list]
+REPLACE_PERL List of Pathmask [$package_list]
+REPLACE_PYTHON List of Pathmask [$package_list]
+REPLACE_SH List of Pathmask [$package_list]
+REQD_DIRS List of Pathname [$package_list]
+REQD_DIRS_PERMS List of ShellWord [$package_list]
+REQD_FILES List of Pathname [$package_list]
+REQD_FILES_MODE { 0644 0640 0600 0400 } [$package]
+REQD_FILES_PERMS List of ShellWord [$package_list]
+RESTRICTED Message [$package]
+ROOT_USER UserGroupName [$user]
+ROOT_GROUP UserGroupName [$user]
+RUBY_VERSION_REQD Version [$user]
+RUN ShellCommand [$system]
+SCRIPTS_ENV List of ShellWord [m:a,c:a]
+SETUID_ROOT_PERMS List of ShellWord [$user]
+SHAREGRP UserGroupName [$system]
+SHAREMODE FileMode [$system]
+SHAREOWN UserGroupName [$system]
+SHCOMMENT ShellCommand [$system]
+SHLIB_HANDLING { YES NO no }
+SHLIBTOOL ShellCommand []
+SHLIBTOOL_OVERRIDE List of Pathmask [m:as,c:a]
+SITES.* List of FetchURL [m:asu,c:asu,o:asu]
+SPECIAL_PERMS List of ShellWord [$package_list]
+STEP_MSG ShellCommand [$system]
+SUBDIR List of Filename [Makefile:a,*:]
+SUBST_CLASSES List of Identifier [m:a,c:a,h:a,Makefile.*:a]
+SUBST_FILES.* List of Pathmask [m:as,c:as,h:as,o:as,Makefile.*:as]
+SUBST_FILTER_CMD.* ShellCommand [m:s,c:s,h:s,o:s,Makefile.*:s]
+SUBST_MESSAGE.* Message [m:s,c:s,h:s,o:s,Makefile.*:s]
+SUBST_SED.* SedCommands [m:as,c:as,h:as,o:as,Makefile.*:as]
+SUBST_STAGE.* Stage [$package]
+SUBST_VARS.* List of Varname [$package_list]
+SUPERSEDES InternalList of Dependency [$package_list]
+TEST_DIRS List of WrksrcSubdirectory [$package_list]
+TEST_ENV List of ShellWord [$package_list]
+TEST_TARGET List of Identifier [m:s,c:ds,o:as]
+TEX_ACCEPTED List of { teTeX1 teTeX2 teTeX3 } [m:s,c:s]
+TEX_DEPMETHOD { build run } [m:s,c:s]
+TEXINFO_REQD List of Version [$package_list]
+TOOL_DEPENDS InternalList of DependencyWithPath [c:a,m:a,o:a,*.mk:a]
+TOOLS_ALIASES List of Filename [$system]
+TOOLS_BROKEN List of Tool [$system]
+TOOLS_CREATE List of Tool [$system]
+TOOLS_DEPENDS.* InternalList of DependencyWithPath [$system]
+TOOLS_GNU_MISSING List of Tool [$system]
+TOOLS_NOOP List of Tool [$system]
+TOOLS_PATH.* Pathname [$system]
+TOOLS_PLATFORM.* ShellCommand [$system]
+TOUCH_FLAGS List of ShellWord [$system]
+UAC_REQD_EXECS List of PrefixPathname [$package_list]
+UNLIMIT_RESOURCES List of { datasize stacksize memorysize } [m:as,c:a]
+UNPRIVILEGED_USER UserGroupName [$user]
+UNPRIVILEGED_GROUP UserGroupName [$user]
+UNWRAP_FILES List of Pathmask [$package_list]
+UPDATE_TARGET List of Identifier [$user]
+USE_BSD_MAKEFILE Yes [$package]
+USE_BUILTIN.* YesNo_Indirectly [builtin.mk:s]
+USE_CMAKE Yes [$package]
+USE_CROSSBASE Yes [m:s]
+USE_FEATURES List of Identifier [$package]
+USE_GCC_RUNTIME YesNo [$package]
+USE_GNU_CONFIGURE_HOST YesNo [$package]
+USE_GNU_ICONV Yes [m:s,c:s,o:s]
+USE_IMAKE Yes [m:s]
+USE_JAVA { run yes build } [$package]
+USE_JAVA2 { YES yes no 1.4 1.5 6 7 8 } [$package]
+USE_LANGUAGES List of { ada c c99 c++ fortran fortran77 java objc } [m:s,c:s,o:s]
+USE_LIBTOOL Yes [$package]
+USE_MAKEINFO Yes [$package]
+USE_MSGFMT_PLURALS Yes [$package]
+USE_NCURSES Yes [$package]
+USE_OLD_DES_API YesNo [$package]
+USE_PKGINSTALL Yes [$package]
+USE_PKGLOCALEDIR YesNo [$package]
+USE_PKGSRC_GCC Yes [$user]
+USE_TOOLS List of Tool [*:a]
+USE_X11 Yes [$package]
+WARNING_MSG ShellCommand [$system]
+WARNING_CAT ShellCommand [$system]
+WRAPPER_REORDER_CMDS List of WrapperReorder [b:a,c:a,m:a]
+WRAPPER_TRANSFORM_CMDS List of WrapperTransform [b:a,c:a,m:a]
+WRKDIR Pathname [$system]
+WRKSRC WrkdirSubdirectory [$package]
+X11_PKGSRCDIR.* Pathname [$system]
+XAW_TYPE { 3d neXtaw standard xpm } [$user]
+XMKMF_FLAGS List of ShellWord
diff --git a/pkgtools/pkglint4/files/pkglint.0 b/pkgtools/pkglint4/files/pkglint.0
new file mode 100644
index 00000000000..d814bdaf4e6
--- /dev/null
+++ b/pkgtools/pkglint4/files/pkglint.0
@@ -0,0 +1,213 @@
+PKGLINT(1) General Commands Manual PKGLINT(1)
+
+NNAAMMEE
+ ppkkgglliinntt -- static analyzer for pkgsrc packages
+
+SSYYNNOOPPSSIISS
+ ppkkgglliinntt [--ooppttiioonnss] [_d_i_r _._._.]
+
+DDEESSCCRRIIPPTTIIOONN
+ ppkkgglliinntt attempts to detect features of the named pkgsrc packages that are
+ likely to be bugs, or that are simply deprecated.
+
+
+ OOppttiioonnss
+ --CC{{[[nnoo--]]cchheecckk,,......}} Enable or disable specific checks. For a list of
+ checks, see below.
+
+ --DD{{[[nnoo--]]ddeebbuugg,,......}} Enable or disable debugging categories. For a list
+ of categories, see below.
+
+ --FF|----aauuttooffiixx Repair trivial things automatically.
+
+ --II Show the _M_a_k_e_f_i_l_e that is constructed by including
+ all the files that are slurped in via `.include'
+ directives. This flag is mainly for debugging.
+
+ --RR|----rrccssiiddssttrriinngg Allow other RCS Id strings than NetBSD. Multiple
+ strings may be concatenated with `|'.
+
+ --VV|----vveerrssiioonn Print the current ppkkgglliinntt version number and exit.
+
+ --WW{{[[nnoo--]]wwaarrnn,,......}} Enable or disable specific warnings. For a list of
+ warnings, see below.
+
+ --ee|----eexxppllaaiinn Print further explanations for diagnostics.
+ Sometimes the reasons for diagnostics are not obvious
+ and need further explanation.
+
+ --gg|----ggcccc--oouuttppuutt--ffoorrmmaatt
+ Use a format for the diagnostics that is understood
+ by most programs, especially editors, so they can
+ provide a point-and-goto interface.
+
+ --hh|----hheellpp Show the summary of command line options, then exit.
+
+ --ii|----iimmppoorrtt Check if a package is ready to be imported into
+ pkgsrc. This is especially useful for packages from
+ the pkgsrc-wip project.
+
+ --qq|----qquuiieett Don't print the errors and warnings summary before
+ terminating.
+
+ --rr|----rreeccuurrssiivvee Check subdirectories, too. The subdirectories are
+ those that are mentioned in a `SUBDIR+=' line.
+
+ --ss|----ssoouurrccee For all diagnostics having file and line number
+ information, show the source code along with the
+ diagnostics.
+
+ CChheecckkss
+ aallll Enable all checks.
+
+ nnoonnee Disable all checks.
+
+ [[nnoo--]]AALLTTEERRNNAATTIIVVEESS Check the alternatives file.
+
+ [[nnoo--]]DDEESSCCRR Check the DESCR file.
+
+ [[nnoo--]]IINNSSTTAALLLL Check the INSTALL and DEINSTALL scripts.
+
+ [[nnoo--]]MMaakkeeffiillee Check the package Makefile, including all included
+ files.
+
+ [[nnoo--]]MMEESSSSAAGGEE Check MESSAGE files.
+
+ [[nnoo--]]PPLLIISSTT Check PLIST files.
+
+ [[nnoo--]]bbll33 Check buildlink3 Makefiles.
+
+ [[nnoo--]]ddiissttiinnffoo Check the distinfo file.
+
+ [[nnoo--]]eexxttrraa Check remaining files in the package directory.
+
+ [[nnoo--]]mmkk Check Makefile fragments besides buildlink3.
+
+ [[nnoo--]]ppaattcchheess Check the pkgsrc specific patch files.
+
+ DDeebbuuggggiinngg OOppttiioonnss
+ aallll Enable all debugging options.
+
+ nnoonnee Disable all debugging options.
+
+ [[nnoo--]]iinncclluuddee Show the pathnames of the included Makefiles.
+
+ [[nnoo--]]mmiisscc Some debugging stuff that hasn't made it into its own
+ category.
+
+ [[nnoo--]]ppaattcchheess Print the states of the patch file parser.
+
+ [[nnoo--]]qquuoottiinngg Additional information about why variables should be
+ quoted or not.
+
+ [[nnoo--]]sshheellll Parser information from the shell word and the shell
+ command parsers.
+
+ [[nnoo--]]ttoooollss Additional information about the tools from the tools
+ framework.
+
+ [[nnoo--]]ttrraaccee Print the names of subroutines and their arguments as
+ they are entered.
+
+ [[nnoo--]]uunncchheecckkeedd Show the things that pkglint cannot currently check.
+ These are mostly due to unresolved make variables.
+
+ [[nnoo--]]uunnuusseedd Show which variables are detected as used, and so
+ will not generate an ``unused variable'' warning.
+
+ [[nnoo--]]vvaarrttyyppeess Additional information about the variable types.
+
+ [[nnoo--]]vvaarruussee Information about the contexts in which variables are
+ used.
+
+ WWaarrnniinnggss
+ aallll Enable all warnings.
+
+ nnoonnee Disable all warnings.
+
+ [[nnoo--]]aabbssnnaammee Warn if a file contains an absolute pathname.
+
+ [[nnoo--]]ddiirreeccttccmmdd Warn if a system command name is used instead of a
+ variable (e.g. sed instead of ${SED}).
+
+ [[nnoo--]]eexxttrraa Emit some additional warnings that are not enabled by
+ default, for whatever reason.
+
+ [[nnoo--]]oorrddeerr Warn if Makefile variables are not in the preferred
+ order.
+
+ [[nnoo--]]ppeerrmm Warn if a variable is used or defined outside its
+ specified scope. The available permissions are:
+ append
+ append something using +=
+ default
+ set a default value using ?=
+ preprocess
+ use a variable during preprocessing
+ runtime
+ use a variable at runtime
+ set set a variable using :=, =, !=
+ unknown
+ permissions for this variable currently not
+ known
+ A `?' means that it is not yet clear which
+ permissions are allowed and which aren't.
+
+ [[nnoo--]]pplliisstt--ddeepprr Warn if deprecated pathnames are used in _P_L_I_S_T files.
+ This warning is disabled by default.
+
+ [[nnoo--]]pplliisstt--ssoorrtt Warn if items of a PLIST file are not sorted
+ alphabetically. This warning is disabled by default.
+
+ [[nnoo--]]qquuoottiinngg Warn for possibly invalid quoting of make variables
+ in shell programs and shell variables themselves.
+
+ [[nnoo--]]ssppaaccee Emit notes for inconsistent use of white-space.
+
+ [[nnoo--]]ssttyyllee Warn for stylistic issues that don't affect the build
+ process.
+
+ [[nnoo--]]ttyyppeess Warn for some _M_a_k_e_f_i_l_e variables if their assigned
+ values do not match their type.
+
+ [[nnoo--]]vvaarroorrddeerr Warn if the variables in a package _M_a_k_e_f_i_l_es are not
+ ordered in the way it is described the pkgsrc guide.
+
+ OOtthheerr aarrgguummeennttss
+ _d_i_r _._._. The pkgsrc directories to be checked. If
+ omitted, the current directory is checked.
+
+FFIILLEESS
+ _p_k_g_s_r_c_/_m_k_/_* Files from the pkgsrc infrastructure.
+
+EEXXAAMMPPLLEESS
+ ppkkgglliinntt --CCnnoonnee,,ppaattcchheess ..
+ Checks the patches of the package in the current directory.
+
+ ppkkgglliinntt --WWaallll //uussrr//ppkkggssrrcc//ddeevveell
+ Checks the category Makefile and reports any warnings it can
+ find.
+
+ ppkkgglliinntt --rr --RR ''NNeettBBSSDD||IIdd'' //uussrr//ppkkggssrrcc
+ Check the whole pkgsrc tree while allowing `NetBSD' or `Id'
+ as the RCS Id.
+
+DDIIAAGGNNOOSSTTIICCSS
+ Diagnostics are written to the standard output.
+
+ ERROR: ... Errors should be fixed before a package is committed to
+ pkgsrc.
+
+ WARN: ... Warnings generally should be fixed, but they are not as
+ critical as errors.
+
+AAUUTTHHOORRSS
+ Roland Illig <rillig@NetBSD.org>
+
+BBUUGGSS
+ Many more checks could be added.
+
+ If you don't understand the messages, feel free to ask on the
+ <tech-pkg@NetBSD.org> mailing list.
+
+NetBSD 6.99.8 July 14, 2012 NetBSD 6.99.8
diff --git a/pkgtools/pkglint4/files/pkglint.1 b/pkgtools/pkglint4/files/pkglint.1
new file mode 100644
index 00000000000..811746f41b9
--- /dev/null
+++ b/pkgtools/pkglint4/files/pkglint.1
@@ -0,0 +1,244 @@
+.\" $NetBSD: pkglint.1,v 1.1 2015/11/25 16:42:21 rillig Exp $
+.\" From FreeBSD: 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.
+.\"
+.\" Roland Illig <roland.illig@gmx.de>, 2004, 2005.
+.\" Thomas Klausner <wiz@NetBSD.org>, 2012.
+.\"
+.Dd July 14, 2012
+.Dt PKGLINT 1
+.Os
+.Sh NAME
+.Nm pkglint
+.Nd static analyzer for pkgsrc packages
+.Sh SYNOPSIS
+.Nm pkglint
+.Op Fl options
+.Op Ar dir ...
+.Sh DESCRIPTION
+.Nm
+attempts to detect features of the named pkgsrc packages that are likely
+to be bugs, or that are simply deprecated.
+.Pp
+.\" =======================================================================
+.Ss Options
+.Bl -tag -width 18n
+.It Fl C{[no-]check,...}
+Enable or disable specific checks.
+For a list of checks, see below.
+.It Fl D{[no-]debug,...}
+Enable or disable debugging categories.
+For a list of categories, see below.
+.It Fl F Ns | Ns Fl -autofix
+Repair trivial things automatically.
+.It Fl I
+Show the
+.Pa Makefile
+that is constructed by including all the files that
+are slurped in via
+.Ql .include
+directives.
+This flag is mainly for debugging.
+.It Fl R Ns | Ns Fl -rcsidstring
+Allow other RCS Id strings than NetBSD.
+Multiple strings may be concatenated with
+.Ql | .
+.It Fl V Ns | Ns Fl -version
+Print the current
+.Nm
+version number and exit.
+.It Fl W{[no-]warn,...}
+Enable or disable specific warnings.
+For a list of warnings, see below.
+.It Fl e Ns | Ns Fl -explain
+Print further explanations for diagnostics.
+Sometimes the reasons for diagnostics are not obvious and need further
+explanation.
+.It Fl g Ns | Ns Fl -gcc-output-format
+Use a format for the diagnostics that is understood by most programs,
+especially editors, so they can provide a point-and-goto interface.
+.It Fl h Ns | Ns Fl -help
+Show the summary of command line options, then exit.
+.It Fl i Ns | Ns Fl -import
+Check if a package is ready to be imported into pkgsrc.
+This is especially useful for packages from the pkgsrc-wip project.
+.It Fl q Ns | Ns Fl -quiet
+Don't print the errors and warnings summary before terminating.
+.It Fl r Ns | Ns Fl -recursive
+Check subdirectories, too.
+The subdirectories are those that are mentioned in a
+.Ql SUBDIR+=
+line.
+.It Fl s Ns | Ns Fl -source
+For all diagnostics having file and line number information, show the
+source code along with the diagnostics.
+.El
+.\" =======================================================================
+.Ss Checks
+.Bl -tag -width 18n
+.It Cm all
+Enable all checks.
+.It Cm none
+Disable all checks.
+.It Cm [no-]ALTERNATIVES
+Check the alternatives file.
+.It Cm [no-]DESCR
+Check the DESCR file.
+.It Cm [no-]INSTALL
+Check the INSTALL and DEINSTALL scripts.
+.It Cm [no-]Makefile
+Check the package Makefile, including all included files.
+.It Cm [no-]MESSAGE
+Check MESSAGE files.
+.It Cm [no-]PLIST
+Check PLIST files.
+.It Cm [no-]bl3
+Check buildlink3 Makefiles.
+.It Cm [no-]distinfo
+Check the distinfo file.
+.It Cm [no-]extra
+Check remaining files in the package directory.
+.It Cm [no-]mk
+Check Makefile fragments besides buildlink3.
+.It Cm [no-]patches
+Check the pkgsrc specific patch files.
+.El
+.\" =======================================================================
+.Ss Debugging Options
+.Bl -tag -width 18n
+.It Cm all
+Enable all debugging options.
+.It Cm none
+Disable all debugging options.
+.It Cm [no-]include
+Show the pathnames of the included Makefiles.
+.It Cm [no-]misc
+Some debugging stuff that hasn't made it into its own category.
+.It Cm [no-]patches
+Print the states of the patch file parser.
+.It Cm [no-]quoting
+Additional information about why variables should be quoted or not.
+.It Cm [no-]shell
+Parser information from the shell word and the shell command parsers.
+.It Cm [no-]tools
+Additional information about the tools from the tools framework.
+.It Cm [no-]trace
+Print the names of subroutines and their arguments as they are entered.
+.It Cm [no-]unchecked
+Show the things that pkglint cannot currently check.
+These are mostly due to unresolved make variables.
+.It Cm [no-]unused
+Show which variables are detected as used, and so will not generate an
+.Dq unused variable
+warning.
+.It Cm [no-]vartypes
+Additional information about the variable types.
+.It Cm [no-]varuse
+Information about the contexts in which variables are used.
+.El
+.\" =======================================================================
+.Ss Warnings
+.Bl -tag -width 18n
+.It Cm all
+Enable all warnings.
+.It Cm none
+Disable all warnings.
+.It Cm [no-]absname
+Warn if a file contains an absolute pathname.
+.It Cm [no-]directcmd
+Warn if a system command name is used instead of a variable (e.g. sed
+instead of ${SED}).
+.It Cm [no-]extra
+Emit some additional warnings that are not enabled by default,
+for whatever reason.
+.It Cm [no-]order
+Warn if Makefile variables are not in the preferred order.
+.It Cm [no-]perm
+Warn if a variable is used or defined outside its specified scope.
+The available permissions are:
+.Bl -tag -width 3n -compact
+.It append
+append something using +=
+.It default
+set a default value using ?=
+.It preprocess
+use a variable during preprocessing
+.It runtime
+use a variable at runtime
+.It set
+set a variable using :=, =, !=
+.It unknown
+permissions for this variable currently not known
+.El
+A
+.Sq \&?
+means that it is not yet clear which permissions are allowed and which aren't.
+.It Cm [no-]plist-depr
+Warn if deprecated pathnames are used in
+.Pa PLIST
+files.
+This warning is disabled by default.
+.It Cm [no-]plist-sort
+Warn if items of a PLIST file are not sorted alphabetically.
+This warning is disabled by default.
+.It Cm [no-]quoting
+Warn for possibly invalid quoting of make variables in shell programs
+and shell variables themselves.
+.It Cm [no-]space
+Emit notes for inconsistent use of white-space.
+.It Cm [no-]style
+Warn for stylistic issues that don't affect the build process.
+.It Cm [no-]types
+Warn for some
+.Pa Makefile
+variables if their assigned values do not match
+their type.
+.It Cm [no-]varorder
+Warn if the variables in a package
+.Pa Makefile Ns
+s are not ordered in the way it is described the pkgsrc guide.
+.El
+.\" =======================================================================
+.Ss Other arguments
+.Bl -tag -width 18n -offset indent
+.It Ar dir ...
+The pkgsrc directories to be checked.
+If omitted, the current directory is checked.
+.El
+.Sh FILES
+.Bl -tag -width pkgsrc/mk/* -compact
+.It Pa pkgsrc/mk/*
+Files from the pkgsrc infrastructure.
+.El
+.Sh EXAMPLES
+.Bl -tag -width Fl
+.It Ic pkglint \-Cnone,patches \&.
+Checks the patches of the package in the current directory.
+.It Ic pkglint \-Wall /usr/pkgsrc/devel
+Checks the category Makefile and reports any warnings it can find.
+.It Ic pkglint -r \-R 'NetBSD|Id' /usr/pkgsrc
+Check the whole pkgsrc tree while allowing
+.Ql NetBSD
+or
+.Ql Id
+as the RCS Id.
+.El
+.Sh DIAGNOSTICS
+Diagnostics are written to the standard output.
+.Bl -tag -width "WARN: foobaa"
+.It ERROR: ...
+Errors should be fixed before a package is committed to pkgsrc.
+.It WARN: ...
+Warnings generally should be fixed, but they are not as critical as
+errors.
+.El
+.Sh AUTHORS
+.An Roland Illig Aq Mt rillig@NetBSD.org
+.Sh BUGS
+Many more checks could be added.
+.Pp
+If you don't understand the messages, feel free to ask on the
+.Aq tech-pkg@NetBSD.org
+mailing list.
diff --git a/pkgtools/pkglint4/files/pkglint.pl b/pkgtools/pkglint4/files/pkglint.pl
new file mode 100644
index 00000000000..ddb6c1f8c48
--- /dev/null
+++ b/pkgtools/pkglint4/files/pkglint.pl
@@ -0,0 +1,5915 @@
+#! @PERL@
+# $NetBSD: pkglint.pl,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+# pkglint - static analyzer and checker for pkgsrc packages
+#
+# Written by:
+# Roland Illig <rillig@NetBSD.org>
+#
+# Based on work by:
+# Hubert Feyrer <hubertf@NetBSD.org>
+# Thorsten Frueauf <frueauf@NetBSD.org>
+# Thomas Klausner <wiz@NetBSD.org>
+# and others.
+#
+# Based on FreeBSD's portlint by:
+# Jun-ichiro itojun Hagino <itojun@itojun.org>
+# Yoshishige Arai <ryo2@on.rim.or.jp>
+#
+# FreeBSD Id: portlint.pl,v 1.64 1998/02/28 02:34:05 itojun Exp
+# Copyright(c) 1997 by Jun-ichiro Hagino <itojun@itojun.org>.
+# All rights reserved.
+# Freely redistributable. Absolutely no warranty.
+
+# To get an overview of the code, run:
+# sed -n -e 's,^\(sub .*\) {.*, \1,p' -e '/^package/p' pkglint.pl
+
+#==========================================================================
+# Note: The @EXPORT clauses in the packages must be in a BEGIN block,
+# because otherwise the names starting with an uppercase letter are not
+# recognized as subroutines but as file handles.
+#==========================================================================
+
+use v5.12;
+use warnings;
+
+#include PkgLint/Util.pm
+#include PkgLint/Logging.pm
+#include PkgLint/SimpleMatch.pm
+#include PkgLint/Line.pm
+#include PkgLint/FileUtil.pm
+#include PkgLint/Type.pm
+#include PkgLint/VarUseContext.pm
+#include PkgLint/SubstContext.pm
+#include PkgLint/CVS_Entry.pm
+#include PkgLint/Change.pm
+
+package pkglint;
+#==========================================================================
+# This package contains the application-specific code of pkglint.
+# Most subroutines in this package follow a strict naming convention:
+#
+# The get_*() functions provide easy access to important non-trivial data
+# structures that are loaded from external files and are therefore cached.
+#
+# The is_*() functions return a boolean value and have no side effects.
+#
+# The checkline_*() procedures check a single line for compliance with some
+# rules.
+#
+# The checklines_*() procedures check an array of lines for compliance.
+# Usually they make use of several checkline_*() procedures.
+#
+# The checkfile_*() procedures load a file and check the lines of that
+# file. Usually they make use of several checklines_*() and checkline_*()
+# procedures.
+#
+# The checkdir_*() procedures check the files of a directory and call
+# checkfile_*() on them.
+#
+# Note: I have tried to order the subroutines so that there are no
+# back-references, that is, if you start reading the code from the top to
+# the bottom you should not find a call to a subroutine you haven't yet
+# seen.
+#==========================================================================
+use strict;
+use warnings;
+
+use Data::Dumper;
+use Digest::SHA1;
+use Getopt::Long qw(:config no_ignore_case bundling require_order);
+use Fcntl qw(:mode);
+use File::Basename;
+use File::stat;
+use Cwd;
+use pkgsrc::Dewey;
+
+BEGIN {
+ import PkgLint::Util qw(
+ array_to_hash assert
+ false true dont_know doesnt_matter
+ normalize_pathname
+ );
+ import PkgLint::Logging qw(
+ NO_FILE NO_LINE_NUMBER NO_LINES
+ log_fatal log_error log_warning log_note log_debug
+ explain_error explain_warning explain_info
+ );
+ import PkgLint::FileUtil qw(
+ load_file load_lines
+ save_autofix_changes
+ );
+ import PkgLint::Type qw(
+ LK_NONE LK_INTERNAL LK_EXTERNAL
+ GUESSED NOT_GUESSED
+ );
+ import PkgLint::VarUseContext qw(
+ VUC_TIME_UNKNOWN VUC_TIME_LOAD VUC_TIME_RUN
+ VUC_TYPE_UNKNOWN
+ VUC_SHELLWORD_UNKNOWN VUC_SHELLWORD_PLAIN VUC_SHELLWORD_DQUOT
+ VUC_SHELLWORD_SQUOT VUC_SHELLWORD_BACKT VUC_SHELLWORD_FOR
+ VUC_EXTENT_UNKNOWN VUC_EXTENT_FULL VUC_EXTENT_WORD
+ VUC_EXTENT_WORD_PART
+ );
+}
+
+#
+# Buildtime configuration
+#
+
+use constant conf_distver => '@DISTVER@';
+use constant conf_make => '@MAKE@';
+use constant conf_datadir => '@DATADIR@';
+
+#
+# Global variables that can be modified via command line options.
+#
+
+# The pkgsrc directory, relative to the current working directory of
+# pkglint.
+my $cwd_pkgsrcdir = undef;
+
+# The pkgsrc directory, relative to the directory that is currently
+# checked.
+my $cur_pkgsrcdir = undef;
+
+#
+# Command Line Options
+#
+
+my $opt_check_ALTERNATIVES = true;
+my $opt_check_bl3 = true;
+my $opt_check_DESCR = true;
+my $opt_check_distinfo = true;
+my $opt_check_extra = false;
+my $opt_check_global = false;
+my $opt_check_INSTALL = true;
+my $opt_check_Makefile = true;
+my $opt_check_MESSAGE = true;
+my $opt_check_mk = true;
+my $opt_check_patches = true;
+my $opt_check_PLIST = true;
+my (%checks) = (
+ "ALTERNATIVES" => [\$opt_check_ALTERNATIVES, "check ALTERNATIVES files"],
+ "bl3" => [\$opt_check_bl3, "check buildlink3 files"],
+ "DESCR" => [\$opt_check_DESCR, "check DESCR file"],
+ "distinfo" => [\$opt_check_distinfo, "check distinfo file"],
+ "extra" => [\$opt_check_extra, "check various additional files"],
+ "global" => [\$opt_check_global, "inter-package checks"],
+ "INSTALL" => [\$opt_check_INSTALL, "check INSTALL and DEINSTALL scripts"],
+ "Makefile" => [\$opt_check_Makefile, "check Makefiles"],
+ "MESSAGE" => [\$opt_check_MESSAGE, "check MESSAGE files"],
+ "mk" => [\$opt_check_mk, "check other .mk files"],
+ "patches" => [\$opt_check_patches, "check patches"],
+ "PLIST" => [\$opt_check_PLIST, "check PLIST files"],
+);
+
+my $opt_debug_include = false;
+my $opt_debug_misc = false;
+my $opt_debug_patches = false;
+my $opt_debug_quoting = false;
+my $opt_debug_shell = false;
+my $opt_debug_tools = false;
+my $opt_debug_trace = false;
+my $opt_debug_unchecked = false;
+my $opt_debug_unused = false;
+my $opt_debug_vartypes = false;
+my $opt_debug_varuse = false;
+my (%debug) = (
+ "include" => [\$opt_debug_include, "included files"],
+ "misc" => [\$opt_debug_misc, "all things that didn't fit elsewhere"],
+ "patches" => [\$opt_debug_patches, "the states of the patch parser"],
+ "quoting" => [\$opt_debug_quoting, "additional information about quoting"],
+ "shell" => [\$opt_debug_shell, "the parsers for shell words and shell commands"],
+ "tools" => [\$opt_debug_tools, "the tools framework"],
+ "trace" => [\$opt_debug_trace, "follow subroutine calls"],
+ "unchecked" => [\$opt_debug_unchecked, "show the current limitations of pkglint"],
+ "unused" => [\$opt_debug_unused, "unused variables"],
+ "vartypes" => [\$opt_debug_vartypes, "additional type information"],
+ "varuse" => [\$opt_debug_varuse, "contexts where variables are used"],
+);
+
+my $opt_warn_absname = true;
+my $opt_warn_directcmd = true;
+our $opt_warn_extra = false; # used by PkgLint::SubstContext
+my $opt_warn_order = true;
+my $opt_warn_perm = false;
+my $opt_warn_plist_depr = false;
+my $opt_warn_plist_sort = false;
+my $opt_warn_quoting = false;
+my $opt_warn_space = false;
+my $opt_warn_style = false;
+my $opt_warn_types = true;
+my $opt_warn_varorder = false;
+my (%warnings) = (
+ "absname" => [\$opt_warn_absname, "warn about use of absolute file names"],
+ "directcmd" => [\$opt_warn_directcmd, "warn about use of direct command names instead of Make variables"],
+ "extra" => [\$opt_warn_extra, "enable some extra warnings"],
+ "order" => [\$opt_warn_order, "warn if Makefile entries are unordered"],
+ "perm" => [\$opt_warn_perm, "warn about unforeseen variable definition and use"],
+ "plist-depr" => [\$opt_warn_plist_depr, "warn about deprecated paths in PLISTs"],
+ "plist-sort" => [\$opt_warn_plist_sort, "warn about unsorted entries in PLISTs"],
+ "quoting" => [\$opt_warn_quoting, "warn about quoting issues"],
+ "space" => [\$opt_warn_space, "warn about inconsistent use of white-space"],
+ "style" => [\$opt_warn_style, "warn about stylistic issues"],
+ "types" => [\$opt_warn_types, "do some simple type checking in Makefiles"],
+ "varorder" => [\$opt_warn_varorder, "warn about the ordering of variables"],
+);
+
+my $opt_autofix = false;
+my $opt_dumpmakefile = false;
+my $opt_import = false;
+my $opt_quiet = false;
+my $opt_recursive = false;
+my $opt_rcsidstring = "NetBSD";
+my (@options) = (
+ # [ usage-opt, usage-message, getopt-opt, getopt-action ]
+ [ "-C{check,...}", "Enable or disable specific checks",
+ "check|C=s",
+ sub {
+ my ($opt, $val) = @_;
+ parse_multioption($val, \%checks);
+ } ],
+ [ "-D{debug,...}", "Enable or disable debugging categories",
+ "debugging|D=s",
+ sub ($$) {
+ my ($opt, $val) = @_;
+ parse_multioption($val, \%debug);
+ } ],
+ [ "-F|--autofix", "Try to automatically fix some errors (experimental)",
+ "autofix|F", \$opt_autofix ],
+ [ "-I|--dumpmakefile", "Dump the Makefile after parsing",
+ "dumpmakefile|I", \$opt_dumpmakefile ],
+ [ "-R|--rcsidstring", "Set the allowed RCS Id strings",
+ "rcsidstring|R=s", \$opt_rcsidstring ],
+ [ "-V|--version", "print the version number of pkglint",
+ "version|V",
+ sub {
+ print(conf_distver . "\n");
+ exit(0);
+ } ],
+ [ "-W{warn,...}", "enable or disable specific warnings",
+ "warning|W=s",
+ sub {
+ my ($opt, $val) = @_;
+ parse_multioption($val, \%warnings);
+ } ],
+ [ "-e|--explain", "Explain the diagnostics or give further help",
+ "explain|e", sub {
+ PkgLint::Logging::set_explain();
+ } ],
+ [ "-g|--gcc-output-format", "Mimic the gcc output format",
+ "gcc-output-format|g",
+ sub {
+ PkgLint::Logging::set_gcc_output_format();
+ } ],
+ [ "-h|--help", "print a detailed help message",
+ "help|h",
+ sub {
+ help(*STDOUT, 0, 1);
+ } ],
+ [ "-i|--import", "Prepare the import of a wip package",
+ "import|i", \$opt_import ],
+ # Note: This is intentionally undocumented.
+ [ "--pkgsrcdir", "Set the root directory of pkgsrc explicitly.",
+ "pkgsrcdir=s", \$cwd_pkgsrcdir ],
+ [ "-q|--quiet", "Don't print a summary line when finishing",
+ "quiet|q", \$opt_quiet ],
+ [ "-r|--recursive", "Recursive---check subdirectories, too",
+ "recursive|r", \$opt_recursive ],
+ [ "-s|--source", "Show the source lines together with diagnostics",
+ "source|s",
+ sub {
+ PkgLint::Logging::set_show_source_flag();
+ } ],
+);
+
+our $program = $0;
+
+#
+# Commonly used regular expressions.
+#
+
+use constant regex_dependency_lge => qr"^((?:\$\{[\w_]+\}|[\w_\.+]|-[^\d])+)[<>]=?(\d[^-*?\[\]]*)$";
+use constant regex_dependency_wildcard
+ => qr"^((?:\$\{[\w_]+\}|[\w_\.+]|-[^\d\[])+)-(?:\[0-9\]\*|\d[^-]*)$";
+use constant regex_gnu_configure_volatile_vars
+ => qr"^(?:.*_)?(?:CFLAGS||CPPFLAGS|CXXFLAGS|FFLAGS|LDFLAGS|LIBS)$";
+use constant regex_mk_comment => qr"^ *\s*#(.*)$";
+use constant regex_mk_cond => qr"^\.(\s*)(if|ifdef|ifndef|else|elif|endif|for|endfor|undef)(?:\s+([^\s#][^#]*?))?\s*(?:#.*)?$";
+use constant regex_mk_dependency=> qr"^([^\s:]+(?:\s*[^\s:]+)*)(\s*):\s*([^#]*?)(?:\s*#.*)?$";
+use constant regex_mk_include => qr"^\.\s*(s?include)\s+\"([^\"]+)\"\s*(?:#.*)?$";
+use constant regex_mk_sysinclude=> qr"^\.\s*s?include\s+<([^>]+)>\s*(?:#.*)?$";
+use constant regex_mk_shellvaruse => qr"(?:^|[^\$])\$\$\{?(\w+)\}?"; # XXX: not perfect
+use constant regex_pkgname => qr"^([\w\-.+]+)-(\d(?:\w|\.\d)*)$";
+use constant regex_mk_shellcmd => qr"^\t(.*)$";
+use constant regex_rcs_conflict => qr"^(<<<<<<<|=======|>>>>>>>)";
+use constant regex_unresolved => qr"\$\{";
+use constant regex_validchars => qr"[\011\040-\176]";
+# Note: the following regular expression looks more complicated than
+# necessary to avoid a stack overflow in the Perl interpreter.
+# The leading white-space may only consist of \040 characters, otherwise
+# the order of regex_varassign and regex_mk_shellcmd becomes important.
+use constant regex_varassign => qr"^ *([-*+A-Z_a-z0-9.\${}\[]+?)\s*(=|\?=|\+=|:=|!=)\s*((?:[^\\#\s]+|\s+?|(?:\\#)+|\\)*?)(?:\s*(#.*))?$";
+use constant regex_sh_varassign => qr"^([A-Z_a-z][0-9A-Z_a-z]*)=";
+
+# The following "constants" are often used in contexts where
+# interpolation comes handy, so they are variables. Nevertheless they
+# are not modified.
+
+# This regular expression cannot parse all kinds of shell programs, but
+# it will catch almost all shell programs that are portable enough to be
+# used in pkgsrc.
+my $regex_shellword = qr"\s*(
+ \#.* # shell comment
+ |
+ (?: '[^']*' # single quoted string
+ | \"(?:\\.|[^\"\\])*\" # double quoted string
+ | \`[^\`]*\` # backticks string
+ | \\\$\$ # an escaped dollar sign
+ | \\[^\$] # other escaped characters
+ | \$[\w_] # one-character make(1) variable
+ | \$\{[^{}]+\} # make(1) variable
+ | \$\([^()]+\) # make(1) variable, $(...)
+ | \$[/\@<^] # special make(1) variables
+ | \$\$[0-9A-Z_a-z]+ # shell variable
+ | \$\$[\#?@] # special shell variables
+ | \$\$\$\$ # the special pid shell variable
+ | \$\$\{[0-9A-Z_a-z]+\} # shell variable in braces
+ | \$\$\( # POSIX-style backticks replacement
+ | [^\(\)'\"\\\s;&\|<>\`\$] # non-special character
+ | \$\{[^\s\"'`]+ # HACK: nested make(1) variables
+ )+ | ;;? | &&? | \|\|? | \( | \) | >& | <<? | >>? | \#.*)"sx;
+my $regex_varname = qr"(?:[-*+.0-9A-Z_a-z{}\[]+|\$\{[\w_]+\})+";
+my $regex_pkgbase = qr"(?:[+.0-9A-Z_a-z]|-[A-Z_a-z])+";
+my $regex_pkgversion = qr"\d(?:\w|\.\d)*";
+
+#
+# Commonly used explanations for diagnostics.
+#
+
+use constant expl_relative_dirs => (
+ "Directories in the form \"../../category/package\" make it easier to",
+ "move a package around in pkgsrc, for example from pkgsrc-wip to the",
+ "main pkgsrc repository.");
+
+#
+# Global variables.
+#
+
+my $current_dir; # The currently checked directory.
+my $is_wip; # Is the current directory from pkgsrc-wip?
+my $is_internal; # Is the current item from the infrastructure?
+
+#
+# Variables for inter-package checks.
+#
+
+my $ipc_distinfo; # Maps "$alg:$fname" => "checksum".
+my %ipc_used_licenses; # { license name => true }
+my $ipc_checking_root_recursively; # For checking unused licenses
+
+# Context of the package that is currently checked.
+my $pkgpath; # The relative path to the package within PKGSRC
+my $pkgdir; # PKGDIR from the package Makefile
+my $filesdir; # FILESDIR from the package Makefile
+my $patchdir; # PATCHDIR from the package Makefile
+my $distinfo_file; # DISTINFO_FILE from the package Makefile
+my $effective_pkgname; # PKGNAME or DISTNAME from the package Makefile
+my $effective_pkgbase; # The effective PKGNAME without the version
+my $effective_pkgversion; # The version part of the effective PKGNAME
+my $effective_pkgname_line; # The origin of the three effective_* values
+my $seen_bsd_prefs_mk; # Has bsd.prefs.mk already been included?
+
+my $pkgctx_vardef; # { varname => line }
+my $pkgctx_varuse; # { varname => line }
+my $pkgctx_bl3; # { buildlink3.mk name => line } (contains
+ # only buildlink3.mk files that are directly
+ # included)
+my $pkgctx_plist_subst_cond; # { varname => 1 } list of all variables
+ # that are used as conditionals (@comment
+ # or nothing) in PLISTs.
+my $pkgctx_included; # { fname => line }
+my $seen_Makefile_common; # Does the package have any .includes?
+
+# Context of the Makefile that is currently checked.
+my $mkctx_for_variables; # The variables currently used in .for loops
+my $mkctx_indentations; # Indentation depth of preprocessing directives
+my $mkctx_target; # Current make(1) target
+my $mkctx_vardef; # { varname => line } for all variables that
+ # are defined in the current file
+my $mkctx_varuse; # { varname => line } for all variables
+ # that are used in the current file
+my $mkctx_build_defs; # Set of variables that are registered in
+ # BUILD_DEFS, to assure that all user-defined
+ # variables are added to it.
+my $mkctx_plist_vars; # The same for PLIST_VARS.
+my $mkctx_tools; # Set of tools that are declared to be used.
+
+my @todo_items; # The list of directory entries that still need
+ # to be checked. Mostly relevant with
+ # --recursive.
+
+#
+# Command line parsing and handling.
+#
+
+sub help($$$) {
+ my ($out, $exitval, $show_all) = @_;
+ my ($prog) = (basename($program, '.pl'));
+ print $out ("usage: $prog [options] [package_directory]\n\n");
+
+ my (@option_table) = ();
+ foreach my $opt (@options) {
+ push(@option_table, [" ", $opt->[0], $opt->[1]]);
+ }
+ print $out ("options:\n");
+ PkgLint::Util::print_table($out, \@option_table);
+ print $out ("\n");
+
+ if (!$show_all) {
+ exit($exitval);
+ }
+
+ my $categories = [
+ # options, leading text,
+ [ \%checks, "checks", "check" ],
+ [ \%debug, "debugging options", "debug" ],
+ [ \%warnings, "warnings", "warning" ],
+ ];
+ foreach my $category (@{$categories}) {
+ my ($options, $leading, $name) = (@{$category});
+ my $table = [
+ [" ", "all", "", "enable all ".$category->[1]],
+ [" ", "none", "", "disable all ".$category->[1]],
+ ];
+
+ foreach my $opt (sort keys %{$options}) {
+ push(@{$table}, [ " ", $opt,
+ (${$options->{$opt}->[0]} ? "(enabled)" : "(disabled)"),
+ $options->{$opt}->[1]]);
+ }
+
+ print $out ("${leading}: (use \"${name}\" to enable, \"no-${name}\" to disable)\n");
+ PkgLint::Util::print_table($out, $table);
+ print $out ("\n");
+ }
+
+ exit($exitval);
+}
+
+sub parse_multioption($$) {
+ my ($value, $optdefs) = @_;
+ foreach my $opt (split(qr",", $value)) {
+ if ($opt eq "none") {
+ foreach my $key (keys %{$optdefs}) {
+ ${$optdefs->{$key}->[0]} = false;
+ }
+
+ } elsif ($opt eq "all") {
+ foreach my $key (keys %{$optdefs}) {
+ ${$optdefs->{$key}->[0]} = true;
+ }
+
+ } else {
+ my ($value) = (($opt =~ s/^no-//) ? false : true);
+ if (exists($optdefs->{$opt})) {
+ ${$optdefs->{$opt}->[0]} = $value;
+ } else {
+ print STDERR ("Invalid option: ${opt}\n");
+ help(*STDERR, 1, 0);
+ }
+ }
+ }
+}
+
+sub parse_command_line() {
+ my (%options);
+
+ foreach my $opt (@options) {
+ $options{$opt->[2]} = $opt->[3];
+ }
+
+ {
+ local $SIG{__WARN__} = sub {};
+ if (!GetOptions(%options)) {
+ help(*STDERR, 1, false);
+ }
+ }
+}
+
+#
+# Caching subroutines.
+#
+
+# The get_regex_plurals() function returns a regular expression that
+# matches for all make(1) variable names that are considered lists
+# of something.
+#
+# Rationale:
+#
+# The pkglint author thinks that variables containing lists of things
+# should have a name indicating some plural form. Sadly, there are other
+# reasons like backwards compatibility and other developer's
+# expectations that make changes to most of the following variables
+# highly unlikely.
+sub get_regex_plurals() {
+ state $result = undef;
+ return $result if defined($result);
+
+ my @plurals_ok = qw(
+ .*S
+ .*LIST
+ .*_AWK
+ .*_ENV
+ .*_REQD
+ .*_SED
+ .*_SKIP
+ BUILDLINK_LDADD
+ COMMENT
+ EXTRACT_ONLY
+ FETCH_MESSAGE
+ GENERATE_PLIST
+ PLIST_CAT
+ PLIST_PRE
+ PREPEND_PATH
+ );
+ my @plurals_missing_an_s = qw(
+ .*_OVERRIDE
+ .*_PREREQ
+ .*_SRC
+ .*_SUBST
+ .*_TARGET
+ .*_TMPL
+ BROKEN_EXCEPT_ON_PLATFORM
+ BROKEN_ON_PLATFORM
+ BUILDLINK_DEPMETHOD
+ BUILDLINK_TRANSFORM
+ EVAL_PREFIX
+ INTERACTIVE_STAGE
+ LICENSE
+ MASTER_SITE_.*
+ MASTER_SORT_REGEX
+ NOT_FOR_COMPILER
+ NOT_FOR_PLATFORM
+ ONLY_FOR_COMPILER
+ ONLY_FOR_PLATFORM
+ PERL5_PACKLIST
+ PKG_FAIL_REASON
+ PKG_SKIP_REASON
+ );
+ my @plurals_reluctantly_accepted = qw(
+ CRYPTO
+ DEINSTALL_TEMPLATE
+ FIX_RPATH
+ INSTALL_TEMPLATE
+ PYTHON_VERSIONS_INCOMPATIBLE
+ REPLACE_INTERPRETER
+ REPLACE_PERL
+ REPLACE_RUBY
+ RESTRICTED
+ SITES_.*
+ TOOLS_ALIASES\.*
+ TOOLS_BROKEN
+ TOOLS_CREATE
+ TOOLS_GNU_MISSING
+ TOOLS_NOOP
+ );
+ my $plurals = join("|",
+ @plurals_ok,
+ @plurals_missing_an_s,
+ @plurals_reluctantly_accepted
+ );
+
+ $result = qr"^(?:${plurals})$";
+ return $result;
+}
+
+#
+# Loading pkglint-specific data from files.
+#
+
+sub load_existing_lines($$) {
+ my ($fname, $fold_backslash_lines) = @_;
+
+ my $lines = load_lines($fname, $fold_backslash_lines)
+ or log_fatal($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return $lines;
+}
+
+# The symbol table for ACL definitions maps ACL names to ACLs.
+my $acl_definitions = {};
+
+sub parse_acls($$) {
+ my ($line, $acltext) = @_;
+ my ($acls);
+
+ use constant ACL_shortcuts => {
+ "b" => qr"(?:^|/)buildlink3\.mk$",
+ "c" => qr"(?:^|/)Makefile\.common$",
+ "h" => qr"(?:^|/)hacks\.mk$",
+ "m" => qr"(?:^|/)Makefile$",
+ "o" => qr"(?:^|/)options\.mk$",
+ };
+
+ my $regex_acl_entry = qr"^(?:
+ \$([\w_]+) # $acl_name
+ | ([\w.*]+|_):([adpsu]*) # file*mask:perms
+ ) (?:\,\s*|$)"x;
+
+ if (!defined($acltext)) {
+ return undef;
+ }
+
+ $acls = [];
+ while ($acltext =~ s,$regex_acl_entry,,) {
+ my ($acldef, $subject, $perms) = ($1, $2, $3);
+
+ if (defined($acldef)) {
+ if (!exists($acl_definitions->{$acldef})) {
+ $line->log_fatal("ACL definition ${acldef} not found.");
+ } else {
+ push(@{$acls}, @{$acl_definitions->{$acldef}});
+ }
+
+ } else {
+ # Transform $subject to a regular expression.
+ $subject =~ s/\./[.]/g;
+ $subject =~ s/\*/.*/g;
+
+ push(@{$acls}, [exists(ACL_shortcuts->{$subject}) ? ACL_shortcuts->{$subject} : qr"(?:^|/)${subject}$", $perms]);
+ }
+ }
+ if ($acltext ne "") {
+ $line->log_fatal("Invalid ACL: ${acltext}.");
+ }
+
+ return $acls;
+}
+
+sub get_vartypes_basictypes() {
+ state $result = undef;
+ return $result if defined($result);
+
+ my $lines = load_file($program);
+ my $types = {};
+ assert($lines, "Couldn't load pkglint.pl from $program");
+ foreach my $line (@$lines) {
+ if ($line->text =~ m"^\s+(\w+) => sub \{$") {
+ # XXX lookup in %type_dispatch instead
+ $types->{$1} = 1;
+ }
+ }
+ return ($result = $types);
+}
+
+sub get_vartypes_map() {
+ state $result = undef;
+ return $result if defined($result);
+
+ my ($fname, $vartypes);
+
+ use constant re_acl_def => qr"^
+ acl \s+
+ (\w+) \s+ # ACL name
+ = \s+
+ \[ ([^\]]*) \] # ACL value
+ (?:\s*\#.*)? # optional comment
+ $"x;
+
+ use constant re_vartypedef => qr"^
+ ([\w\d_.]+?) # $1 = variable name
+ (\*|\.\*|) \s+ # $2 = parameterized?
+ (?:(InternalList|List) \s+ of \s+)? # $3 ?= kind of list
+ (?:([\w\d_]+) # $4 ?= basic type
+ | \{\s*([\w\d_+,\-.\s]+?)\s*\}) # $5 ?= enumeration values
+ (?:\s+ \[ ([^\]]*) \])? # $6 ?= optional ACL
+ (?:\s*\#.*)? # $7 ?= optional comment
+ $"x;
+
+ $fname = conf_datadir."/makevars.map";
+ $vartypes = {};
+
+ my $lines = load_existing_lines($fname, true);
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ m"^(?:#.*|\s*)$") {
+ # ignore empty and comment lines
+
+ } elsif ($line->text =~ re_acl_def) {
+ my ($aclname, $aclvalue) = ($1, $2);
+
+ $acl_definitions->{$aclname} = parse_acls($line, $aclvalue);
+
+ } elsif ($line->text =~ re_vartypedef) {
+ my ($varname, $par, $kind_of_list_text, $typename, $enums, $acltext) = ($1, $2, $3, $4, $5, $6);
+ my $kind_of_list = !defined($kind_of_list_text) ? LK_NONE
+ : ($kind_of_list_text eq "List") ? LK_EXTERNAL
+ : LK_INTERNAL;
+
+ if (defined($typename) && !exists(get_vartypes_basictypes()->{$typename})) {
+ $line->log_fatal("Unknown basic type \"$typename\" for variable $varname. "
+ . "Valid basic types are "
+ . join(", ", sort keys %{get_vartypes_basictypes()})
+ . ".");
+ }
+
+ my $basic_type = defined($enums)
+ ? array_to_hash(split(qr"\s+", $enums))
+ : $typename;
+ my $type = PkgLint::Type->new($kind_of_list, $basic_type, parse_acls($line, $acltext), NOT_GUESSED);
+ if ($par eq "" || $par eq "*") {
+ $vartypes->{$varname} = $type;
+ }
+ if ($par eq "*" || $par eq ".*") {
+ $vartypes->{"${varname}.*"} = $type;
+ }
+
+ } else {
+ $line->log_fatal("Unknown line format.");
+ }
+ }
+
+# TODO: Enable when the time is ripe.
+if (false) {
+ # Additionally, scan mk/defaults/mk.conf for variable
+ # definitions. All these variables are reserved for the user and
+ # must not be set within packages.
+ $fname = "${cwd_pkgsrcdir}/mk/defaults/mk.conf";
+ if ((my $lines = load_file($fname))) {
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ m"^#?([\w_]+)\?=") {
+ my ($varname) = ($1);
+ $opt_debug_misc and $line->log_debug("Found user-definable variable ${varname}.");
+ $vartypes->{$varname} = "Userdefined"; # FIXME: type error
+ }
+ }
+ } else {
+ log_fatal($fname, NO_LINE_NUMBER, "Cannot be read.");
+ }
+}
+
+ return ($result = $vartypes);
+}
+
+sub get_deprecated_map() {
+ state $result = undef;
+ return $result if defined($result);
+
+ my ($fname, $lines, $vars);
+
+ $fname = conf_datadir."/deprecated.map";
+ if (!($lines = load_file($fname))) {
+ log_fatal($fname, NO_LINE_NUMBER, "Cannot be read.");
+ }
+
+ $vars = {};
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ m"^#" || $line->text =~ m"^\s*$") {
+ # Ignore empty and comment lines.
+
+ } elsif ($line->text =~ m"^(\S+)\s+(.*)$") {
+ $vars->{$1} = $2;
+
+ } else {
+ $line->log_fatal("Unknown line format.");
+ }
+ }
+ return ($result = $vars);
+}
+
+my $load_dist_sites_url2name = undef;
+my $load_dist_sites_names = undef;
+sub load_dist_sites() {
+ my ($fname) = ("${cwd_pkgsrcdir}/mk/fetch/sites.mk");
+ my ($lines) = load_file($fname);
+ my ($varname) = undef;
+ my ($ignoring) = false;
+ my ($url2name) = {};
+ my ($names) = {};
+
+ if (!$lines) {
+ log_error($fname, NO_LINE_NUMBER, "Could not be read.");
+ $load_dist_sites_url2name = $url2name;
+ $load_dist_sites_names = $names;
+ return;
+ }
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ if ($text =~ m"^(MASTER_SITE_\w+)\+=\s*\\$"o) {
+ $varname = $1;
+ $names->{$varname} = true;
+ $ignoring = false;
+
+ } elsif ($text eq "MASTER_SITE_BACKUP?=\t\\") {
+ $ignoring = true;
+
+ } elsif ($text =~ m"^\t((?:http://|https://|ftp://)\S+/)(?:|\s*\\)$"o) {
+ if (!$ignoring) {
+ if (defined($varname)) {
+ $url2name->{$1} = $varname;
+ } else {
+ $line->log_error("Lonely URL found.");
+ }
+ }
+
+ } elsif ($text =~ m"^(?:#.*|\s*)$") {
+ # ignore empty and comment lines
+
+ } elsif ($text =~ m"BSD_SITES_MK") {
+ # ignore multiple inclusion guards
+
+ } else {
+ $line->log_fatal("Unknown line type.");
+ }
+ }
+
+ # Explicitly allowed, although not defined in mk/fetch/sites.mk.
+ $names->{"MASTER_SITE_SUSE_UPD"} = true;
+ $names->{"MASTER_SITE_LOCAL"} = true;
+
+ $opt_debug_misc and log_debug($fname, NO_LINES, "Loaded " . scalar(keys(%{$url2name})) . " MASTER_SITE_* definitions.");
+ $load_dist_sites_url2name = $url2name;
+ $load_dist_sites_names = $names;
+}
+
+sub get_dist_sites() {
+ if (!defined($load_dist_sites_url2name)) {
+ load_dist_sites();
+ }
+ return $load_dist_sites_url2name;
+}
+
+sub get_dist_sites_names() {
+ if (!defined($load_dist_sites_names)) {
+ load_dist_sites();
+ }
+ return $load_dist_sites_names;
+}
+
+sub get_pkg_options() {
+ state $result = undef;
+ return $result if defined($result);
+
+ my ($fname) = ("${cwd_pkgsrcdir}/mk/defaults/options.description");
+ my ($lines, $options);
+
+ if (!($lines = load_file($fname))) {
+ log_fatal($fname, NO_LINE_NUMBER, "Cannot be read.");
+ }
+
+ $options = {};
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ m"^([-0-9a-z_+]+)(?:\s+(.*))?$") {
+ my ($optname, $optdescr) = ($1, $2);
+
+ $options->{$optname} = defined($optdescr)
+ ? $optdescr
+ : "";
+ } else {
+ $line->log_error("Unknown line format.");
+ }
+ }
+
+ return ($result = $options);
+}
+
+my $load_tool_names_system_build_defs = undef; # XXX: misplaced, but works
+my $load_tool_names_tools = undef;
+my $load_tool_names_vartools = undef;
+my $load_tool_names_varname_to_toolname = undef;
+my $load_tool_names_predefined_tools = undef;
+sub load_tool_names() {
+ my ($tools, $vartools, $predefined_tools, $varname_to_toolname, @tool_files);
+ my ($system_build_defs);
+
+ #
+ # Get the list of files that define the tools from bsd.tools.mk.
+ #
+
+ @tool_files = ("defaults.mk");
+ {
+ my $fname = "${cwd_pkgsrcdir}/mk/tools/bsd.tools.mk";
+ my $lines = load_existing_lines($fname, true);
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ regex_mk_include) {
+ my (undef, $includefile) = ($1, $2);
+ if ($includefile =~ m"^(?:\$\{PKGSRCDIR\}/mk/tools/)?([^/]+)$") {
+ push(@tool_files, $1);
+ }
+ }
+ }
+ }
+ assert(scalar(@tool_files) > 1, "Too few tool files. Maybe the files have been renamed again?");
+
+ #
+ # Scan the tool files for the actual definitions of the tools.
+ #
+
+ $tools = {};
+ $vartools = {};
+ $predefined_tools = {};
+ $varname_to_toolname = {};
+ $system_build_defs = {};
+ foreach my $basename (@tool_files) {
+ my $fname = "${cwd_pkgsrcdir}/mk/tools/${basename}";
+ my $lines = load_existing_lines($fname, true);
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ regex_varassign) {
+ my ($varname, undef, $value, undef) = ($1, $2, $3, $4);
+ if ($varname eq "TOOLS_CREATE" && $value =~ m"^([-\w.]+|\[)$") {
+ $tools->{$value} = true;
+
+ } elsif ($varname =~ m"^(?:_TOOLS_VARNAME)\.([-\w.]+|\[)$") {
+ $tools->{$1} = true;
+ $vartools->{$1} = $value;
+ $varname_to_toolname->{$value} = $1;
+
+ } elsif ($varname =~ m"^(?:TOOLS_PATH|_TOOLS_DEPMETHOD)\.([-\w.]+|\[)$") {
+ $tools->{$1} = true;
+
+ } elsif ($varname =~ m"^_TOOLS\.(.*)") {
+ $tools->{$1} = true;
+ foreach my $tool (split(qr"\s+", $value)) {
+ $tools->{$tool} = true;
+ }
+ }
+ }
+ }
+ }
+
+ foreach my $basename ("bsd.pkg.mk") {
+ my $fname = "${cwd_pkgsrcdir}/mk/${basename}";
+ my $cond_depth = 0;
+
+ my $lines = load_existing_lines($fname, true);
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ if ($text =~ regex_varassign) {
+ my ($varname, undef, $value, undef) = ($1, $2, $3, $4);
+
+ if ($varname eq "USE_TOOLS") {
+ $opt_debug_tools and $line->log_debug("[cond_depth=${cond_depth}] $value");
+ if ($cond_depth == 0) {
+ foreach my $tool (split(qr"\s+", $value)) {
+ if ($tool !~ regex_unresolved && exists($tools->{$tool})) {
+ $predefined_tools->{$tool} = true;
+ # The path (without arguments) to the tool
+ $predefined_tools->{"TOOLS_${tool}"} = true;
+ }
+ }
+ }
+ } elsif ($varname eq "_BUILD_DEFS") {
+ foreach my $bdvar (split(qr"\s+", $value)) {
+ $system_build_defs->{$bdvar} = true;
+ }
+ }
+
+ } elsif ($text =~ regex_mk_cond) {
+ my ($indent, $cond, $args, $comment) = ($1, $2, $3, $4);
+
+ if ($cond =~ m"^(?:if|ifdef|ifndef|for)$") {
+ $cond_depth++;
+ } elsif ($cond =~ m"^(?:endif|endfor)$") {
+ $cond_depth--;
+ }
+ }
+ }
+ }
+
+ $opt_debug_tools and log_debug(NO_FILE, NO_LINE_NUMBER, "Known tools: ".join(" ", sort(keys(%{$tools}))));
+ $opt_debug_tools and log_debug(NO_FILE, NO_LINE_NUMBER, "Known vartools: ".join(" ", sort(keys(%{$vartools}))));
+ $opt_debug_tools and log_debug(NO_FILE, NO_LINE_NUMBER, "Predefined tools: " . join(" ", sort(keys(%{$predefined_tools}))));
+ $opt_debug_tools and log_debug(NO_FILE, NO_LINE_NUMBER, "Known varnames: " . join(" ", sort(keys(%{$varname_to_toolname}))));
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "System-provided BUILD_DEFS: " . join(" ", sort(keys(%{$system_build_defs}))));
+
+ # Some user-defined variables do not influence the binary
+ # package at all and therefore do not have to be added to
+ # BUILD_DEFS.
+ foreach my $bdvar (qw(DISTDIR FETCH_CMD FETCH_OUTPUT_ARGS GAMES_USER GAMES_GROUP GAMEDATAMODE GAMEDIRMODE GAMEMODE GAMEOWN GAMEGRP )) {
+ $system_build_defs->{$bdvar} = true;
+ }
+ #$system_build_defs->{"PACKAGES"} = true;
+
+ $load_tool_names_tools = $tools;
+ $load_tool_names_vartools = $vartools;
+ $load_tool_names_predefined_tools = $predefined_tools;
+ $load_tool_names_varname_to_toolname = $varname_to_toolname;
+ $load_tool_names_system_build_defs = $system_build_defs;
+}
+
+# Returns the set of known tool names and contains for example "sed" and
+# "gm4".
+sub get_tool_names() {
+
+ if (!defined($load_tool_names_tools)) {
+ load_tool_names();
+ }
+ return $load_tool_names_tools;
+}
+
+# Returns the mapping from tool names to their respective variable. For
+# example, "sed" => "SED", "gzip" => "GZIP_CMD".
+sub get_vartool_names() {
+
+ if (!defined($load_tool_names_vartools)) {
+ load_tool_names();
+ }
+ return $load_tool_names_vartools;
+}
+
+# Returns the set of those tools with associated variables that a
+# package does not need to add to USE_TOOLS explicitly because they
+# are used by the pkgsrc infrastructure, too.
+sub get_predefined_tool_names() {
+ if (!defined($load_tool_names_predefined_tools)) {
+ load_tool_names();
+ }
+ return $load_tool_names_predefined_tools;
+}
+
+# Returns a mapping from tool variable names to the tool name they use.
+# For example, "GZIP_CMD" => "gzip" and "SED" => "sed".
+sub get_varname_to_toolname() {
+ if (!defined($load_tool_names_varname_to_toolname)) {
+ load_tool_names();
+ }
+ return $load_tool_names_varname_to_toolname;
+}
+
+# Returns the set of tool variable names that may not be converted to
+# their "direct" form, that is: ${CP} => cp.
+sub get_required_vartool_varnames() {
+ use constant required_vartool_varnames => array_to_hash(qw(ECHO ECHO_N FALSE TEST TRUE));
+
+ return required_vartool_varnames;
+}
+
+# Returns the set of tools that must be used by their variable name.
+sub get_required_vartools() {
+ use constant required_vartools => array_to_hash(qw(echo false test true));
+
+ return required_vartools;
+}
+
+
+# Returns the set of user-defined variables that are added to BUILD_DEFS
+# within the bsd.pkg.mk file.
+sub get_system_build_defs() {
+ if (!defined($load_tool_names_system_build_defs)) {
+ load_tool_names();
+ }
+ return $load_tool_names_system_build_defs;
+}
+
+sub load_doc_TODO_updates($) {
+ my ($fname) = @_;
+ my ($lines, $updates, $state, $re_suggested_update);
+
+ if (!($lines = load_file($fname))) {
+ log_fatal($fname, NO_LINE_NUMBER, "Cannot be read.");
+ }
+
+ $updates = [];
+ $state = 0;
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ if ($state == 0 && $text eq "Suggested package updates") {
+ $state = 1;
+ } elsif ($state == 1 && $text eq "") {
+ $state = 2;
+ } elsif ($state == 2) {
+ $state = 3;
+ } elsif ($state == 3 && $text eq "") {
+ $state = 4;
+ }
+
+ if ($state == 3) {
+ if ($text =~ m"^\to\s(\S+)(?:\s*(.+))?$") {
+ my ($spuname, $comment) = ($1, $2);
+ if ($spuname =~ regex_pkgname) {
+ push(@{$updates}, [$line, $1, $2, $comment]);
+ } else {
+ $line->log_warning("Invalid package name $spuname");
+ }
+ } else {
+ $line->log_warning("Invalid line format $text");
+ }
+ }
+ }
+
+ return $updates;
+}
+
+sub get_doc_TODO_updates() {
+ state $result = load_doc_TODO_updates("${cwd_pkgsrcdir}/doc/TODO");
+ return $result;
+}
+
+sub get_wip_TODO_updates() {
+ state $result = load_doc_TODO_updates("${cwd_pkgsrcdir}/wip/TODO");
+ return $result;
+}
+
+sub load_doc_CHANGES($) {
+ my ($fname) = @_;
+ my $lines = load_file($fname) or die;
+
+ my $changes = {}; # { pkgpath -> @changes }
+ foreach my $line (@$lines) {
+ my $text = $line->text;
+ next unless $text =~ m"^\t[A-Z]";
+
+ if ($text =~ m"^\t(Updated) (\S+) to (\S+) \[(\S+) (\d\d\d\d-\d\d-\d\d)\]$") {
+ push(@{$changes->{$2}}, PkgLint::Change->new($line, $1, $2, $3, $4, $5));
+ } elsif ($text =~ m"^\t(Added) (\S+) version (\S+) \[(\S+) (\d\d\d\d-\d\d-\d\d)\]$") {
+ push(@{$changes->{$2}}, PkgLint::Change->new($line, $1, $2, $3, $4, $5));
+ } elsif ($text =~ m"^\t(Removed) (\S+) (?:successor (\S+) )?\[(\S+) (\d\d\d\d-\d\d-\d\d)\]$") {
+ push(@{$changes->{$2}}, PkgLint::Change->new($line, $1, $2, undef, $3, $4));
+ } elsif ($text =~ m"^\t(Downgraded) (\S+) to (\S+) \[(\S+) (\d\d\d\d-\d\d-\d\d)\]$") {
+ push(@{$changes->{$2}}, PkgLint::Change->new($line, $1, $2, $3, $4, $5));
+ } elsif ($text =~ m"^\t(Renamed|Moved) (\S+) to (\S+) \[(\S+) (\d\d\d\d-\d\d-\d\d)\]$") {
+ push(@{$changes->{$2}}, PkgLint::Change->new($line, $1, $2, $3, $4, $5));
+ } else {
+ $line->log_warning("Unknown doc/CHANGES line: " . $line->text);
+ $line->explain_warning(
+"Maybe some developer didn't stick to the conventions that have been",
+"established by mk/misc/developer.mk?");
+ }
+ }
+ return $changes;
+}
+
+my $get_doc_CHANGES_docs = undef; # [ $fname, undef or { pkgpath -> @changes } ]
+sub get_doc_CHANGES($) {
+ my ($pkgpath) = @_;
+
+ $opt_debug_trace and log_debug(NO_FILE, NO_LINES, "get_doc_CHANGES(\"$pkgpath\")");
+
+ # Make a reversed list of all the CHANGES-* files, but don't load
+ # them yet.
+ if (!defined($get_doc_CHANGES_docs)) {
+ opendir(DIR, "${cwd_pkgsrcdir}/doc") or die;
+ my @files = readdir(DIR);
+ closedir(DIR) or die;
+ foreach my $file (reverse sort @files) {
+ if ($file =~ m"^CHANGES-(\d+)$" && (0 + $1 >= 2008)) {
+ push(@$get_doc_CHANGES_docs, [ $file, undef ]);
+ }
+ }
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "Found " . (scalar @$get_doc_CHANGES_docs) . " changes files.");
+ }
+
+ # Scan the *-CHANGES files in reverse order until some action
+ # matches the package directory.
+ my @result = ();
+ foreach my $doc (@$get_doc_CHANGES_docs) {
+ if (!defined($doc->[1])) {
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "loading $doc->[0]");
+ $doc->[1] = load_doc_CHANGES("${cwd_pkgsrcdir}/doc/$doc->[0]");
+ }
+
+ foreach my $change (@{$doc->[1]->{$pkgpath}}) {
+ next unless $pkgpath eq $change->pkgpath;
+ push(@result, $change);
+ }
+ if (@result != 0) {
+ return @result;
+ }
+ }
+ return ();
+}
+
+sub get_suggested_package_updates() {
+
+ return ($is_wip)
+ ? get_wip_TODO_updates()
+ : get_doc_TODO_updates();
+}
+
+# Load all variables from mk/defaults/mk.conf. Since pkglint does not
+# load the infrastructure files during normal operation, these
+# definitions have to be added explicitly.
+sub load_userdefined_variables() {
+ my $fname = "${cwd_pkgsrcdir}/mk/defaults/mk.conf";
+ my $vars = {};
+
+ my $lines = load_existing_lines($fname, true);
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ regex_varassign) {
+ my ($varname, $op, $value, $comment) = ($1, $2, $3, $4);
+
+ $vars->{$varname} = $line;
+ }
+ }
+
+ return $vars;
+}
+
+sub get_userdefined_variables() {
+ state $result = load_userdefined_variables();
+ return $result;
+}
+
+#
+# Miscellaneous functions
+#
+
+sub match_all($$) {
+ my ($text, $re) = @_;
+ my ($mm, $rest, $lastpos);
+
+ $mm = [];
+ $rest = $text;
+ $lastpos = 0;
+ pos(undef);
+ while ($rest =~ m/$re/gc) {
+ my @starts = @-;
+ my @ends = @+;
+
+ $lastpos = $ends[0];
+
+ push(@{$mm}, PkgLint::SimpleMatch->new($text, \@starts, \@ends));
+ }
+ return ($mm, substr($rest, $lastpos));
+}
+
+sub autofix($) {
+ my ($lines) = @_;
+
+ if ($opt_autofix) {
+ save_autofix_changes($lines);
+ }
+}
+
+# Checks whether a file is already committed to the CVS repository or not.
+sub is_committed($) {
+ my ($fname) = @_;
+ my ($basename, $entries);
+
+ $basename = basename($fname);
+ $entries = load_file(dirname($fname) . "/CVS/Entries");
+ if (!$entries) {
+ return false;
+ }
+ foreach my $entry (@{$entries}) {
+ if ($entry->text =~ m"^/\Q${basename}\E/") {
+ return true;
+ }
+ }
+ return false;
+}
+
+# Checks whether a directory is practically empty, that is, all it
+# contains are ".", ".." and "CVS", recursively.
+sub is_emptydir($);
+sub is_emptydir($) {
+ my ($dir) = @_;
+ my ($rv);
+
+ if (!opendir(DIR, $dir)) {
+ return true;
+ }
+ my @subdirs = readdir(DIR);
+ closedir(DIR) or die;
+
+ $rv = true;
+ foreach my $subdir (@subdirs) {
+ next if $subdir eq "." || $subdir eq ".." || $subdir eq "CVS";
+ next if -d "${dir}/${subdir}" && is_emptydir("${dir}/${subdir}");
+
+ $rv = false;
+ last;
+ }
+
+ return $rv;
+}
+
+# Returns the list of subdirectories of a directory, except "CVS".
+sub get_subdirs($) {
+ my ($dir) = @_;
+ my (@result) = ();
+
+ if (opendir(DIR, $dir)) {
+ my @subdirs = readdir(DIR);
+ closedir(DIR) or die;
+ foreach my $subdir (@subdirs) {
+ if ($subdir ne "." && $subdir ne ".." && $subdir ne "CVS" && -d "${dir}/${subdir}" && !is_emptydir("${dir}/${subdir}")) {
+ push(@result, $subdir);
+ }
+ }
+ }
+ return @result;
+}
+
+# No package file should ever be executable. Even the INSTALL and
+# DEINSTALL scripts are usually not usable in the form they have in the
+# package, as the pathnames get adjusted during installation. So there is
+# no need to have any file executable.
+sub checkperms($) {
+ my ($fname) = @_;
+
+ if (-f $fname && -x $fname && !is_committed($fname)) {
+ log_warning($fname, NO_LINE_NUMBER, "Should not be executable.");
+ }
+}
+
+sub resolve_relative_path($$) {
+ my ($relpath, $adjust_depth) = @_;
+
+ my $arg = $relpath;
+ $relpath =~ s,\$\{PKGSRCDIR\},$cur_pkgsrcdir,;
+ $relpath =~ s,\$\{\.CURDIR\},.,;
+ $relpath =~ s,\$\{\.PARSEDIR\},.,;
+ $relpath =~ s,\$\{LUA_PKGSRCDIR\},../../lang/lua52,;
+ $relpath =~ s,\$\{PHPPKGSRCDIR\},../../lang/php54,;
+ $relpath =~ s,\$\{SUSE_DIR_PREFIX\},suse100,;
+ $relpath =~ s,\$\{PYPKGSRCDIR\},../../lang/python27,;
+ $relpath =~ s,\$\{FILESDIR\},$filesdir, if defined($filesdir);
+ if ($adjust_depth && $relpath =~ m"^\.\./\.\./([^.].*)$") {
+ $relpath = "${cur_pkgsrcdir}/$1";
+ }
+ if (defined($pkgdir)) {
+ $relpath =~ s,\$\{PKGDIR\},$pkgdir,g;
+ }
+
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "resolve_relative_path: $arg => $relpath");
+ return $relpath;
+}
+
+# Takes two pathnames and returns the relative pathname to get from
+# the first to the second.
+sub relative_path($$) {
+ my ($from, $to) = @_;
+
+ my $abs_from = Cwd::abs_path($from);
+ my $abs_to = Cwd::abs_path($to);
+ if ($abs_to =~ m"^\Q$abs_from/\E(.*)$") {
+ return $1;
+ } elsif ($abs_to eq $abs_from) {
+ return ".";
+ } else {
+ assert(false, "$abs_from is not a prefix of $abs_to");
+ }
+}
+
+sub resolve_variable_rec1($$);
+sub resolve_variable_rec2($$);
+
+sub resolve_variable_rec1($$) {
+ my ($varname, $visited) = @_;
+ $opt_debug_trace and log_debug(NO_FILE, NO_LINES, "resolve_variable_rec1($varname)");
+
+ if (!exists($visited->{$varname})) {
+ $visited->{$varname} = true;
+ if (defined($pkgctx_vardef) && exists($pkgctx_vardef->{$varname})) {
+ return resolve_variable_rec2($pkgctx_vardef->{$varname}->get("value"), $visited);
+ }
+ if (defined($mkctx_vardef) && exists($mkctx_vardef->{$varname})) {
+ return resolve_variable_rec2($mkctx_vardef->{$varname}->get("value"), $visited);
+ }
+ }
+ return "\${$varname}";
+}
+
+sub resolve_variable_rec2($$) {
+ my ($string, $visited) = @_;
+ $opt_debug_trace and log_debug(NO_FILE, NO_LINES, "resolve_variable_rec2(\"$string\")");
+
+ my $expanded = $string;
+ $expanded =~ s/\$\{(\w+)\}/resolve_variable_rec1($1, $visited)/eg;
+ return $expanded;
+}
+
+sub expand_variable($) {
+ my ($varname) = @_;
+
+ return unless exists($pkgctx_vardef->{$varname});
+ my $line = $pkgctx_vardef->{$varname};
+ my $value = $line->get("value");
+
+ $value = resolve_relative_path($value, true);
+ if ($value =~ regex_unresolved) {
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "[expand_variable] Trying harder to resolve variable references in ${varname}=\"${value}\".");
+ $value = resolve_variable_rec2($value, {});
+ if ($value =~ regex_unresolved) {
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINES, "[expand_variable] Failed to resolve ${varname}=\"${value}\".");
+ }
+ }
+ return $value;
+}
+
+sub set_default_value($$) {
+ my ($varref, $value) = @_;
+
+ if (!defined(${$varref}) || ${$varref} =~ regex_unresolved) {
+ ${$varref} = $value;
+ }
+}
+
+sub strip_mk_comment($) {
+ my ($text) = @_;
+
+ $text =~ s/(^|[^\\])#.*/$1/;
+ $text =~ s/\\#/#/g;
+ return $text;
+}
+
+# Removes all uses of make variables from a string.
+sub remove_variables($) {
+ my ($text) = @_;
+
+ while ($text =~ s/\$\{([^{}]*)\}//g) {
+ }
+ return $text;
+}
+
+sub backtrace($) {
+ my $msg = shift();
+ my (@callers);
+
+ my $n = 0;
+ while (my @info = caller($n)) {
+ push(@callers, [$info[2], $info[3]]);
+ $n++;
+ }
+
+ log_debug(NO_FILE, NO_LINE_NUMBER, $msg);
+ for (my $i = $#callers; $i >= 0; $i--) {
+ my $info = $callers[$i];
+ log_debug(NO_FILE, NO_LINE_NUMBER, sprintf(" line %4d called %s", $info->[0], $info->[1]));
+ }
+}
+
+# Returns the number of columns that a string occupies when printed with
+# a tabulator size of 8.
+sub tablen($) {
+ my ($s) = @_;
+ my ($len);
+
+ $len = 0;
+ foreach my $c (split(qr"", $s)) {
+ if ($c eq "\t") {
+ $len = ($len + 7) & ~7;
+ } else {
+ $len++;
+ }
+ }
+ return $len;
+}
+
+sub shell_split($) {
+ my ($text) = @_;
+ my ($words);
+
+ $words = [];
+ while ($text =~ s/^$regex_shellword//) {
+ push(@{$words}, $1);
+ }
+ return (($text =~ m"^\s*$") ? $words : false);
+}
+
+sub varname_base($) {
+ my ($varname) = @_;
+
+ return ($varname =~ m"^(.*?)\..*$") ? $1 : $varname;
+}
+
+sub varname_canon($) {
+ my ($varname) = @_;
+
+ return ($varname =~ m"^(.*?)\..*$") ? "$1.*" : $varname;
+}
+
+sub varname_param($) {
+ my ($varname) = @_;
+
+ return ($varname =~ m"^.*?\.(.*)$") ? $2 : undef;
+}
+
+sub use_var($$) {
+ my ($line, $varname) = @_;
+ my $varcanon = varname_canon($varname);
+
+ if (defined($mkctx_varuse)) {
+ $mkctx_varuse->{$varname} = $line;
+ $mkctx_varuse->{$varcanon} = $line;
+ }
+
+ if (defined($pkgctx_varuse)) {
+ $pkgctx_varuse->{$varname} = $line;
+ $pkgctx_varuse->{$varcanon} = $line;
+ }
+}
+
+sub var_is_used($) {
+ my ($varname) = @_;
+ my $varcanon = varname_canon($varname);
+
+ if (defined($mkctx_varuse)) {
+ return $mkctx_varuse->{$varname} if exists($mkctx_varuse->{$varname});
+ return $mkctx_varuse->{$varcanon} if exists($mkctx_varuse->{$varcanon});
+ }
+ if (defined($pkgctx_varuse)) {
+ return $pkgctx_varuse->{$varname} if exists($pkgctx_varuse->{$varname});
+ return $pkgctx_varuse->{$varcanon} if exists($pkgctx_varuse->{$varcanon});
+ }
+ return false;
+}
+
+sub def_var($$) {
+ my ($line, $varname) = @_;
+ my $varcanon = varname_canon($varname);
+
+ if (defined($mkctx_vardef)) {
+ $mkctx_vardef->{$varname} = $line;
+ $mkctx_vardef->{$varcanon} = $line;
+ }
+
+ if (defined($pkgctx_vardef)) {
+ $pkgctx_vardef->{$varname} = $line;
+ $pkgctx_vardef->{$varcanon} = $line;
+ }
+}
+
+sub var_is_defined($) {
+ my ($varname) = @_;
+ my $varcanon = varname_canon($varname);
+
+ if (defined($mkctx_vardef)) {
+ return $mkctx_vardef->{$varname} if exists($mkctx_vardef->{$varname});
+ return $mkctx_vardef->{$varcanon} if exists($mkctx_vardef->{$varcanon});
+ }
+ if (defined($pkgctx_vardef)) {
+ return $pkgctx_vardef->{$varname} if exists($pkgctx_vardef->{$varname});
+ return $pkgctx_vardef->{$varcanon} if exists($pkgctx_vardef->{$varcanon});
+ }
+ return false;
+}
+
+sub determine_used_variables($) {
+ my ($lines) = @_;
+ my ($rest);
+
+ foreach my $line (@{$lines}) {
+ $rest = $line->text;
+ while ($rest =~ s/(?:\$\{|\$\(|defined\(|empty\()([0-9+.A-Z_a-z]+)[:})]//) {
+ my ($varname) = ($1);
+ use_var($line, $varname);
+ $opt_debug_unused and $line->log_debug("Variable ${varname} is used.");
+ }
+ }
+}
+
+sub extract_used_variables($$) {
+ my ($line, $text) = @_;
+ my ($rest, $result);
+
+ $rest = $text;
+ $result = [];
+ while ($rest =~ s/^(?:[^\$]+|\$[\$*<>?\@]|\$\{([.0-9A-Z_a-z]+)(?::(?:[^\${}]|\$[^{])+)?\})//) {
+ my ($varname) = ($1);
+
+ if (defined($varname)) {
+ push(@{$result}, $varname);
+ }
+ }
+
+ if ($rest ne "") {
+ $opt_debug_misc and $line->log_warning("Could not extract variables: ${rest}");
+ }
+
+ return $result;
+}
+
+sub get_nbpart() {
+ my $line = $pkgctx_vardef->{"PKGREVISION"};
+ return "" unless defined($line);
+ my $pkgrevision = $line->get("value");
+ return "" unless $pkgrevision =~ m"^\d+$";
+ return "" unless $pkgrevision + 0 != 0;
+ return "nb$pkgrevision";
+}
+
+sub check_pkglint_version() {
+ state $done = false;
+ return if $done;
+ $done = true;
+
+ my $lines = load_lines("${cwd_pkgsrcdir}/pkgtools/pkglint/Makefile", true);
+ return unless $lines;
+
+ my $pkglint_version = undef;
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ regex_varassign) {
+ my ($varname, undef, $value, undef) = ($1, $2, $3, $4);
+
+ if ($varname eq "DISTNAME" || $varname eq "PKGNAME") {
+ if ($value =~ regex_pkgname) {
+ $pkglint_version = $2;
+ }
+ }
+ }
+ }
+ return unless defined($pkglint_version);
+
+ if (dewey_cmp($pkglint_version, ">", conf_distver)) {
+ log_note(NO_FILE, NO_LINE_NUMBER, "A newer version of pkglint is available.");
+ } elsif (dewey_cmp($pkglint_version, "<", conf_distver)) {
+ log_error(NO_FILE, NO_LINE_NUMBER, "The pkglint version is newer than the tree to check.");
+ }
+}
+
+# When processing a file using the expect* subroutines below, it may
+# happen that $lineno points past the end of the file. In that case,
+# print the warning without associated source code.
+sub lines_log_warning($$$) {
+ my ($lines, $lineno, $msg) = @_;
+
+ assert(false, "The line number is negative (${lineno}).")
+ unless 0 <= $lineno;
+ assert(@{$lines} != 0, "The lines may not be empty.");
+
+ if ($lineno <= $#{$lines}) {
+ $lines->[$lineno]->log_warning($msg);
+ } else {
+ log_warning($lines->[0]->fname, "EOF", $msg);
+ }
+}
+
+# Checks if the current line ($lines->{${$lineno_ref}}) matches the
+# regular expression, and if it does, increments ${${lineno_ref}}.
+# @param $lines
+# The lines that are checked.
+# @param $lineno_ref
+# A reference to the line number, an integer variable.
+# @param $regex
+# The regular expression to be checked.
+# @return
+# The result of the regular expression match or false.
+sub expect($$$) {
+ my ($lines, $lineno_ref, $regex) = @_;
+ my $lineno = ${$lineno_ref};
+
+ if ($lineno <= $#{$lines} && $lines->[$lineno]->text =~ $regex) {
+ ${$lineno_ref}++;
+ return PkgLint::SimpleMatch->new($lines->[$lineno]->text, \@-, \@+);
+ } else {
+ return false;
+ }
+}
+
+sub expect_empty_line($$) {
+ my ($lines, $lineno_ref) = @_;
+
+ if (expect($lines, $lineno_ref, qr"^$")) {
+ return true;
+ } else {
+ $opt_warn_space and $lines->[${$lineno_ref}]->log_note("Empty line expected.");
+ return false;
+ }
+}
+
+sub expect_text($$$) {
+ my ($lines, $lineno_ref, $text) = @_;
+
+ my $rv = expect($lines, $lineno_ref, qr"^\Q${text}\E$");
+ $rv or lines_log_warning($lines, ${$lineno_ref}, "Expected \"${text}\".");
+ return $rv;
+}
+
+sub expect_re($$$) {
+ my ($lines, $lineno_ref, $re) = @_;
+
+ my $rv = expect($lines, $lineno_ref, $re);
+ $rv or lines_log_warning($lines, ${$lineno_ref}, "Expected text matching $re.");
+ return $rv;
+}
+
+# Returns an object of type Pkglint::Type that represents the type of
+# the variable (maybe guessed based on the variable name), or undef if
+# the type cannot even be guessed.
+#
+sub get_variable_type($$) {
+ my ($line, $varname) = @_;
+ my ($type);
+
+ assert(defined($varname), "The varname parameter must be defined.");
+
+ if (exists(get_vartypes_map()->{$varname})) {
+ return get_vartypes_map()->{$varname};
+ }
+
+ my $varcanon = varname_canon($varname);
+ if (exists(get_vartypes_map()->{$varcanon})) {
+ return get_vartypes_map()->{$varcanon};
+ }
+
+ if (exists(get_varname_to_toolname()->{$varname})) {
+ return PkgLint::Type->new(LK_NONE, "ShellCommand", [[ qr".*", "u" ]], NOT_GUESSED);
+ }
+
+ if ($varname =~ m"^TOOLS_(.*)" && exists(get_varname_to_toolname()->{$1})) {
+ return PkgLint::Type->new(LK_NONE, "Pathname", [[ qr".*", "u" ]], NOT_GUESSED);
+ }
+
+ use constant allow_all => [[ qr".*", "adpsu" ]];
+ use constant allow_runtime => [[ qr".*", "adsu" ]];
+
+ # Guess the datatype of the variable based on
+ # naming conventions.
+ $type = ($varname =~ m"DIRS$") ? PkgLint::Type->new(LK_EXTERNAL, "Pathmask", allow_runtime, GUESSED)
+ : ($varname =~ m"(?:DIR|_HOME)$") ? PkgLint::Type->new(LK_NONE, "Pathname", allow_runtime, GUESSED)
+ : ($varname =~ m"FILES$") ? PkgLint::Type->new(LK_EXTERNAL, "Pathmask", allow_runtime, GUESSED)
+ : ($varname =~ m"FILE$") ? PkgLint::Type->new(LK_NONE, "Pathname", allow_runtime, GUESSED)
+ : ($varname =~ m"PATH$") ? PkgLint::Type->new(LK_NONE, "Pathlist", allow_runtime, GUESSED)
+ : ($varname =~ m"PATHS$") ? PkgLint::Type->new(LK_EXTERNAL, "Pathname", allow_runtime, GUESSED)
+ : ($varname =~ m"_USER$") ? PkgLint::Type->new(LK_NONE, "UserGroupName", allow_all, GUESSED)
+ : ($varname =~ m"_GROUP$") ? PkgLint::Type->new(LK_NONE, "UserGroupName", allow_all, GUESSED)
+ : ($varname =~ m"_ENV$") ? PkgLint::Type->new(LK_EXTERNAL, "ShellWord", allow_runtime, GUESSED)
+ : ($varname =~ m"_CMD$") ? PkgLint::Type->new(LK_NONE, "ShellCommand", allow_runtime, GUESSED)
+ : ($varname =~ m"_ARGS$") ? PkgLint::Type->new(LK_EXTERNAL, "ShellWord", allow_runtime, GUESSED)
+ : ($varname =~ m"_(?:C|CPP|CXX|LD|)FLAGS$") ? PkgLint::Type->new(LK_EXTERNAL, "ShellWord", allow_runtime, GUESSED)
+ : ($varname =~ m"_MK$") ? PkgLint::Type->new(LK_NONE, "Unchecked", allow_all, GUESSED)
+ : ($varname =~ m"^PLIST.") ? PkgLint::Type->new(LK_NONE, "Yes", allow_all, GUESSED)
+ : undef;
+
+ if (defined($type)) {
+ $opt_debug_vartypes and $line->log_debug("The guessed type of ${varname} is \"" . $type->to_string . "\".");
+ return $type;
+ }
+
+ $opt_debug_vartypes and $line->log_debug("No type definition found for ${varcanon}.");
+ return undef;
+}
+
+sub get_variable_perms($$) {
+ my ($line, $varname) = @_;
+
+ my $type = get_variable_type($line, $varname);
+ if (!defined($type)) {
+ $opt_debug_misc and $line->log_debug("No type definition found for ${varname}.");
+ return "adpsu";
+ }
+
+ my $perms = $type->perms($line->fname, $varname);
+ if (!defined($perms)) {
+ $opt_debug_misc and $line->log_debug("No permissions specified for ${varname}.");
+ return "?";
+ }
+ return $perms;
+}
+
+# This function returns whether a variable needs the :Q operator in a
+# certain context. There are four possible outcomes:
+#
+# false: The variable should not be quoted.
+# true: The variable should be quoted.
+# doesnt_matter:
+# Since the values of the variable usually don't contain
+# special characters, it does not matter whether the
+# variable is quoted or not.
+# dont_know: pkglint cannot say whether the variable should be quoted
+# or not, most likely because type information is missing.
+#
+sub variable_needs_quoting($$$) {
+ my ($line, $varname, $context) = @_;
+ my $type = get_variable_type($line, $varname);
+ my ($want_list, $have_list);
+
+ $opt_debug_trace and $line->log_debug("variable_needs_quoting($varname, " . $context->to_string() . ")");
+
+ use constant safe_types => array_to_hash(qw(
+ DistSuffix
+ FileMode Filename
+ Identifier
+ Option
+ Pathname PkgName PkgOptionsVar PkgRevision
+ RelativePkgDir RelativePkgPath
+ UserGroupName
+ Varname Version
+ WrkdirSubdirectory
+ ));
+
+ if (!defined($type) || !defined($context->type)) {
+ return dont_know;
+ }
+
+ # Variables of certain predefined types, as well as all
+ # enumerations, are expected to not require the :Q operator.
+ if (ref($type->basic_type) eq "HASH" || exists(safe_types->{$type->basic_type})) {
+ if ($type->kind_of_list == LK_NONE) {
+ return doesnt_matter;
+
+ } elsif ($type->kind_of_list == LK_EXTERNAL && $context->extent != VUC_EXTENT_WORD_PART) {
+ return false;
+ }
+ }
+
+ # In .for loops, the :Q operator is always misplaced, since
+ # the items are broken up at white-space, not as shell words
+ # like in all other parts of make(1).
+ if ($context->shellword == VUC_SHELLWORD_FOR) {
+ return false;
+ }
+
+ # Determine whether the context expects a list of shell words or
+ # not.
+ $want_list = $context->type->is_practically_a_list() && ($context->shellword == VUC_SHELLWORD_BACKT || $context->extent != VUC_EXTENT_WORD_PART);
+ $have_list = $type->is_practically_a_list();
+
+ $opt_debug_quoting and $line->log_debug("[variable_needs_quoting]"
+ . " varname=$varname"
+ . " context=" . $context->to_string()
+ . " type=" . $type->to_string()
+ . " want_list=" . ($want_list ? "yes" : "no")
+ . " have_list=" . ($have_list ? "yes" : "no")
+ . ".");
+
+ # A shell word may appear as part of a shell word, for example
+ # COMPILER_RPATH_FLAG.
+ if ($context->extent == VUC_EXTENT_WORD_PART && $context->shellword == VUC_SHELLWORD_PLAIN) {
+ if ($type->kind_of_list == LK_NONE && $type->basic_type eq "ShellWord") {
+ return false;
+ }
+ }
+
+ # Assume that the tool definitions don't include very special
+ # characters, so they can safely be used inside any quotes.
+ if (exists(get_varname_to_toolname()->{$varname})) {
+ my $sw = $context->shellword;
+
+ if ($sw == VUC_SHELLWORD_PLAIN && $context->extent != VUC_EXTENT_WORD_PART) {
+ return false;
+
+ } elsif ($sw == VUC_SHELLWORD_BACKT) {
+ return false;
+
+ } elsif ($sw == VUC_SHELLWORD_DQUOT || $sw == VUC_SHELLWORD_SQUOT) {
+ return doesnt_matter;
+
+ } else {
+ # Let the other rules decide.
+ }
+ }
+
+ # Variables that appear as parts of shell words generally need
+ # to be quoted. An exception is in the case of backticks,
+ # because the whole backticks expression is parsed as a single
+ # shell word by pkglint.
+ #
+ # XXX: When the shell word parser gets rewritten the next time,
+ # this test can be refined.
+ if ($context->extent == VUC_EXTENT_WORD_PART && $context->shellword != VUC_SHELLWORD_BACKT) {
+ return true;
+ }
+
+ # Assigning lists to lists does not require any quoting, though
+ # there may be cases like "CONFIGURE_ARGS+= -libs ${LDFLAGS:Q}"
+ # where quoting is necessary. So let's hope that no developer
+ # ever makes the mistake of using :Q when appending a list to
+ # a list.
+ if ($want_list && $have_list) {
+ return doesnt_matter;
+ }
+
+ # Appending elements to a list requires quoting, as well as
+ # assigning a list value to a non-list variable.
+ if ($want_list != $have_list) {
+ return true;
+ }
+
+ $opt_debug_quoting and $line->log_debug("Don't know whether :Q is needed for ${varname}.");
+ return dont_know;
+}
+
+#
+# Parsing.
+#
+
+# Checks whether $tree matches $pattern, and if so, instanciates the
+# variables in $pattern. If they don't match, some variables may be
+# instanciated nevertheless, but the exact behavior is unspecified.
+#
+sub tree_match($$);
+sub tree_match($$) {
+ my ($tree, $pattern) = @_;
+
+ my $d1 = Data::Dumper->new([$tree, $pattern])->Terse(true)->Indent(0);
+ my $d2 = Data::Dumper->new([$pattern])->Terse(true)->Indent(0);
+ $opt_debug_trace and log_debug(NO_FILE, NO_LINES, sprintf("tree_match(%s, %s)", $d1->Dump, $d2->Dump));
+
+ return true if (!defined($tree) && !defined($pattern));
+ return false if (!defined($tree) || !defined($pattern));
+ my $aref = ref($tree);
+ my $pref = ref($pattern);
+ if ($pref eq "SCALAR" && !defined($$pattern)) {
+ $$pattern = $tree;
+ return true;
+ }
+ if ($aref eq "" && ($pref eq "" || $pref eq "SCALAR")) {
+ return $tree eq $pattern;
+ }
+ if ($aref eq "ARRAY" && $pref eq "ARRAY") {
+ return false if scalar(@$tree) != scalar(@$pattern);
+ for (my $i = 0; $i < scalar(@$tree); $i++) {
+ return false unless tree_match($tree->[$i], $pattern->[$i]);
+ }
+ return true;
+ }
+ return false;
+}
+
+# TODO: Needs to be redesigned to handle more complex expressions.
+sub parse_mk_cond($$);
+sub parse_mk_cond($$) {
+ my ($line, $cond) = @_;
+
+ $opt_debug_trace and $line->log_debug("parse_mk_cond(\"${cond}\")");
+
+ my $re_simple_varname = qr"[A-Z_][A-Z0-9_]*(?:\.[\w_+\-]+)?";
+ while ($cond ne "") {
+ if ($cond =~ s/^!//) {
+ return ["not", parse_mk_cond($line, $cond)];
+ } elsif ($cond =~ s/^defined\((${re_simple_varname})\)$//) {
+ return ["defined", $1];
+ } elsif ($cond =~ s/^empty\((${re_simple_varname})\)$//) {
+ return ["empty", $1];
+ } elsif ($cond =~ s/^empty\((${re_simple_varname}):M([^\$:{})]+)\)$//) {
+ return ["empty", ["match", $1, $2]];
+ } elsif ($cond =~ s/^\$\{(${re_simple_varname})\}\s+(==|!=)\s+"([^"\$\\]*)"$//) {
+ return [$2, ["var", $1], ["string", $3]];
+ } else {
+ $opt_debug_unchecked and $line->log_debug("parse_mk_cond: ${cond}");
+ return ["unknown", $cond];
+ }
+ }
+}
+
+sub parse_licenses($) {
+ my ($licenses) = @_;
+
+ $licenses =~ s,\$\{PERL5_LICENSE},gnu-gpl-v2 OR artistic,g;
+ $licenses =~ s,[()]|AND|OR,,g; # XXX: treats OR like AND
+ my @licenses = split(/\s+/, $licenses);
+ return \@licenses;
+}
+
+# This procedure fills in the extra fields of a line, depending on the
+# line type. These fields can later be queried without having to parse
+# them again and again.
+#
+sub parseline_mk($) {
+ my ($line) = @_;
+ my $text = $line->text;
+
+ if ($text =~ regex_varassign) {
+ my ($varname, $op, $value, $comment) = ($1, $2, $3, $4);
+
+ # In variable assignments, a '#' character is preceded
+ # by a backslash. In shell commands, it is interpreted
+ # literally.
+ $value =~ s/\\\#/\#/g;
+
+ $line->set("is_varassign", true);
+ $line->set("varname", $varname);
+ $line->set("varcanon", varname_canon($varname));
+ my $varparam = varname_param($varname);
+ defined($varparam) and $line->set("varparam", $varparam);
+ $line->set("op", $op);
+ $line->set("value", $value);
+ defined($comment) and $line->set("comment", $comment);
+
+ } elsif ($text =~ regex_mk_shellcmd) {
+ my ($shellcmd) = ($1);
+
+ # Shell command lines cannot have embedded comments.
+ $line->set("is_shellcmd", true);
+ $line->set("shellcmd", $shellcmd);
+
+ my ($shellwords, $rest) = match_all($shellcmd, $regex_shellword);
+ $line->set("shellwords", $shellwords);
+ if ($rest !~ m"^\s*$") {
+ $line->set("shellwords_rest", $rest);
+ }
+
+ } elsif ($text =~ regex_mk_comment) {
+ my ($comment) = ($1);
+
+ $line->set("is_comment", true);
+ $line->set("comment", $comment);
+
+ } elsif ($text =~ m"^\s*$") {
+
+ $line->set("is_empty", true);
+
+ } elsif ($text =~ regex_mk_cond) {
+ my ($indent, $directive, $args, $comment) = ($1, $2, $3, $4);
+
+ $line->set("is_cond", true);
+ $line->set("indent", $indent);
+ $line->set("directive", $directive);
+ defined($args) and $line->set("args", $args);
+ defined($comment) and $line->set("comment", $comment);
+
+ } elsif ($text =~ regex_mk_include) {
+ my (undef, $includefile, $comment) = ($1, $2, $3);
+
+ $line->set("is_include", true);
+ $line->set("includefile", $includefile);
+ defined($comment) and $line->set("comment", $comment);
+
+ } elsif ($text =~ regex_mk_sysinclude) {
+ my ($includefile, $comment) = ($1, $2);
+
+ $line->set("is_sysinclude", true);
+ $line->set("includefile", $includefile);
+ defined($comment) and $line->set("comment", $comment);
+
+ } elsif ($text =~ regex_mk_dependency) {
+ my ($targets, $whitespace, $sources, $comment) = ($1, $2, $3, $4);
+
+ $line->set("is_dependency", true);
+ $line->set("targets", $targets);
+ $line->set("sources", $sources);
+ $line->log_warning("Space before colon in dependency line: " . $line->to_string()) if ($whitespace);
+ defined($comment) and $line->set("comment", $comment);
+
+ } elsif ($text =~ regex_rcs_conflict) {
+ # This line is useless
+
+ } else {
+ $line->log_fatal("Unknown line format: " . $line->to_string());
+ }
+}
+
+sub parselines_mk($) {
+ my ($lines) = @_;
+
+ foreach my $line (@{$lines}) {
+ parseline_mk($line);
+ }
+}
+
+#
+# Loading package-specific data from files.
+#
+
+sub readmakefile($$$$);
+sub readmakefile($$$$) {
+ my ($fname, $main_lines, $all_lines, $seen_Makefile_include) = @_;
+ my ($includefile, $dirname, $lines, $is_main_Makefile);
+
+ $lines = load_lines($fname, true);
+ if (!$lines) {
+ return false;
+ }
+ parselines_mk($lines);
+
+ $is_main_Makefile = (@{$main_lines} == 0);
+
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ if ($is_main_Makefile) {
+ push(@{$main_lines}, $line);
+ }
+ push(@{$all_lines}, $line);
+
+ # try to get any included file
+ my $is_include_line = false;
+ if ($text =~ m"^\.\s*include\s+\"(.*)\"$") {
+ $includefile = resolve_relative_path($1, true);
+ if ($includefile =~ regex_unresolved) {
+ if ($fname !~ m"/mk/") {
+ $line->log_note("Skipping include file \"${includefile}\". This may result in false warnings.");
+ }
+
+ } else {
+ $is_include_line = true;
+ }
+ }
+
+ if ($is_include_line) {
+ if ($fname !~ m"buildlink3\.mk$" && $includefile =~ m"^\.\./\.\./(.*)/buildlink3\.mk$") {
+ my ($bl3_file) = ($1);
+
+ $pkgctx_bl3->{$bl3_file} = $line;
+ $opt_debug_misc and $line->log_debug("Buildlink3 file in package: ${bl3_file}");
+ }
+ }
+
+ if ($is_include_line && !exists($seen_Makefile_include->{$includefile})) {
+ $seen_Makefile_include->{$includefile} = true;
+
+ if ($includefile =~ m"^\.\./[^./][^/]*/[^/]+") {
+ $line->log_warning("Relative directories should look like \"../../category/package\", not \"../package\".");
+ $line->explain_warning(expl_relative_dirs);
+ }
+ if ($includefile =~ m"(?:^|/)Makefile.common$"
+ || ($includefile =~ m"^(?:\.\./(\.\./[^/]+/)?[^/]+/)?([^/]+)$"
+ && (!defined($1) || $1 ne "../mk")
+ && $2 ne "buildlink3.mk"
+ && $2 ne "options.mk")) {
+ $opt_debug_include and $line->log_debug("including ${includefile} sets seen_Makefile_common.");
+ $seen_Makefile_common = true;
+ }
+ if ($includefile =~ m"/mk/") {
+ # skip these files
+
+ } else {
+ $dirname = dirname($fname);
+ # Only look in the directory relative to the
+ # current file and in the current working directory.
+ # We don't have an include dir list, like make(1) does.
+ if (!-f "$dirname/$includefile") {
+ $dirname = $current_dir;
+ }
+ if (!-f "$dirname/$includefile") {
+ $line->log_error("Cannot read $dirname/$includefile.");
+ } else {
+ $opt_debug_include and $line->log_debug("Including \"$dirname/$includefile\".");
+ my $last_lineno = $#{$all_lines};
+ readmakefile("$dirname/$includefile", $main_lines, $all_lines, $seen_Makefile_include) or return false;
+
+ # Check that there is a comment in each
+ # Makefile.common that says which files
+ # include it.
+ if ($includefile =~ m"/Makefile\.common$") {
+ my @mc_lines = @{$all_lines}[$last_lineno+1 .. $#{$all_lines}];
+ my $expected = "# used by " . relative_path($cwd_pkgsrcdir, $fname);
+
+ if (!(grep { $_->text eq $expected } @mc_lines)) {
+ my $lineno = 0;
+ while ($lineno < $#mc_lines && $mc_lines[$lineno]->has("is_comment")) {
+ $lineno++;
+ }
+ my $iline = $mc_lines[$lineno];
+ $iline->log_warning("Please add a line \"$expected\" here.");
+ $iline->explain_warning(
+"Since Makefile.common files usually don't have any comments and",
+"therefore not a clearly defined interface, they should at least contain",
+"references to all files that include them, so that it is easier to see",
+"what effects future changes may have.",
+"",
+"If there are more than five packages that use a Makefile.common,",
+"you should think about giving it a proper name (maybe plugin.mk) and",
+"documenting its interface.");
+ $iline->append_before($expected);
+ autofix(\@mc_lines);
+ }
+ }
+ }
+ }
+
+ } elsif ($line->has("is_varassign")) {
+ my ($varname, $op, $value) = ($line->get("varname"), $line->get("op"), $line->get("value"));
+
+ # Record all variables that are defined in these lines, so that they
+ # are not reported as "used but not defined".
+ if ($op ne "?=" || !exists($pkgctx_vardef->{$varname})) {
+ $opt_debug_misc and $line->log_debug("varassign(${varname}, ${op}, ${value})");
+ $pkgctx_vardef->{$varname} = $line;
+ }
+ }
+ }
+
+ return true;
+}
+
+sub load_package_Makefile($$) {
+ my ($fname, $ref_lines) = @_;
+ my ($subr) = "load_package_Makefile";
+ my ($lines, $all_lines, $seen_php_pecl_version);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "load_package_Makefile()");
+
+ if (!readmakefile($fname, $lines = [], $all_lines = [], $pkgctx_included = {})) {
+ log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return false;
+ }
+
+ if ($opt_dumpmakefile) {
+ log_debug(NO_FILE, NO_LINES, "Whole Makefile (with all included files) follows:");
+ foreach my $line (@{$all_lines}) {
+ print($line->to_string() . "\n");
+ }
+ }
+
+ determine_used_variables($all_lines);
+
+ $pkgdir = expand_variable("PKGDIR");
+ set_default_value(\$pkgdir, ".");
+ $distinfo_file = expand_variable("DISTINFO_FILE");
+ set_default_value(\$distinfo_file, "distinfo");
+ $filesdir = expand_variable("FILESDIR");
+ set_default_value(\$filesdir, "files");
+ $patchdir = expand_variable("PATCHDIR");
+ set_default_value(\$patchdir, "patches");
+
+ if (var_is_defined("PHPEXT_MK")) {
+ if (!var_is_defined("USE_PHP_EXT_PATCHES")) {
+ $patchdir = "patches";
+ }
+ if (var_is_defined("PECL_VERSION")) {
+ $distinfo_file = "distinfo";
+ }
+ }
+
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINE_NUMBER, "[${subr}] DISTINFO_FILE=$distinfo_file");
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINE_NUMBER, "[${subr}] FILESDIR=$filesdir");
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINE_NUMBER, "[${subr}] PATCHDIR=$patchdir");
+ $opt_debug_misc and log_debug(NO_FILE, NO_LINE_NUMBER, "[${subr}] PKGDIR=$pkgdir");
+
+ ${$ref_lines} = $lines;
+ return true;
+}
+
+sub warn_about_PLIST_imake_mannewsuffix($) {
+ my ($line) = @_;
+
+ $line->log_warning("IMAKE_MANNEWSUFFIX is not meant to appear in PLISTs.");
+ $line->explain_warning(
+"This is the result of a print-PLIST call that has _not_ been checked",
+"thoroughly by the developer. Please replace the IMAKE_MANNEWSUFFIX with",
+"",
+"\tIMAKE_MAN_SUFFIX for programs,",
+"\tIMAKE_LIBMAN_SUFFIX for library functions,",
+"\tIMAKE_FILEMAN_SUFFIX for file formats,",
+"\tIMAKE_GAMEMAN_SUFFIX for games,",
+"\tIMAKE_MISCMAN_SUFFIX for other man pages.");
+}
+
+#
+# Subroutines to check part of a single line.
+#
+
+sub checkword_absolute_pathname($$) {
+ my ($line, $word) = @_;
+
+ $opt_debug_trace and $line->log_debug("checkword_absolute_pathname(\"${word}\")");
+
+ if ($word =~ m"^/dev/(?:null|tty|zero)$") {
+ # These are defined by POSIX.
+
+ } elsif ($word eq "/bin/sh") {
+ # This is usually correct, although on Solaris, it's pretty
+ # feature-crippled.
+
+ } elsif ($word !~ m"/(?:[a-z]|\$[({])") {
+ # Assume that all pathnames start with a lowercase letter.
+
+ } else {
+ $line->log_warning("Found absolute pathname: ${word}");
+ $line->explain_warning(
+"Absolute pathnames are often an indicator for unportable code. As",
+"pkgsrc aims to be a portable system, absolute pathnames should be",
+"avoided whenever possible.",
+"",
+"A special variable in this context is \${DESTDIR}, which is used in GNU",
+"projects to specify a different directory for installation than what",
+"the programs see later when they are executed. Usually it is empty, so",
+"if anything after that variable starts with a slash, it is considered",
+"an absolute pathname.");
+ }
+}
+
+sub check_unused_licenses() {
+
+ for my $licensefile (glob("${cwd_pkgsrcdir}/licenses/*")) {
+ if (-f $licensefile) {
+ my $licensename = basename($licensefile);
+ if (!exists($ipc_used_licenses{$licensename})) {
+ log_warning($licensefile, NO_LINES, "This license seems to be unused.");
+ }
+ }
+ }
+}
+
+sub checkpackage_possible_downgrade() {
+
+ $opt_debug_trace and log_debug(NO_FILE, NO_LINES, "checkpackage_possible_downgrade");
+
+ return unless defined $effective_pkgname;
+ return unless $effective_pkgname =~ regex_pkgname;
+ my ($pkgbase, $pkgversion) = ($1, $2);
+ my $line = $effective_pkgname_line;
+
+ my @changes = get_doc_CHANGES($pkgpath);
+ if (@changes == 0) {
+ $opt_debug_misc and $line->log_debug("No changes have been recorded for package $pkgpath.");
+ return;
+ }
+
+ my $last_change = $changes[-1];
+ return unless $last_change->action eq "Updated";
+
+ my $last_version = $last_change->version;
+
+ if (dewey_cmp($pkgversion, "<", $last_version)) {
+ $line->log_warning("The package is being downgraded from $last_version to $pkgversion.");
+ }
+}
+
+#
+# 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_warning(
+"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("Line contains invalid characters (" . join(", ", @chars) . ").");
+ }
+}
+
+sub checkline_valid_characters_in_variable($$) {
+ my ($line, $re_validchars) = @_;
+ my ($varname, $rest);
+
+ $varname = $line->get("varname");
+ $rest = $line->get("value");
+
+ $rest =~ s/$re_validchars//g;
+ if ($rest ne "") {
+ my @chars = map { sprintf("0x%02x", ord($_)); } split(//, $rest);
+ $line->log_warning("${varname} contains invalid characters (" . join(", ", @chars) . ").");
+ }
+}
+
+sub checkline_trailing_whitespace($) {
+ my ($line) = @_;
+
+ $opt_debug_trace and $line->log_debug("checkline_trailing_whitespace()");
+
+ if ($line->text =~ /\s+$/) {
+ $line->log_note("Trailing white-space.");
+ $line->explain_note(
+"When a line ends with some white-space, that space is in most cases",
+"irrelevant and can be removed, leading to a \"normal form\" syntax.",
+"",
+"Note: This is mostly for aesthetic reasons.");
+ $line->replace_regex(qr"\s+\n$", "\n");
+ }
+}
+
+sub checkline_rcsid_regex($$$) {
+ my ($line, $prefix_regex, $prefix) = @_;
+ my ($id) = ($opt_rcsidstring . ($is_wip ? "|Id" : ""));
+
+ $opt_debug_trace and $line->log_debug("checkline_rcsid_regex(${prefix_regex}, ${prefix})");
+
+ if ($line->text !~ m"^${prefix_regex}\$(${id})(?::[^\$]+|)\$$") {
+ $line->log_error("\"${prefix}\$${opt_rcsidstring}\$\" expected.");
+ $line->explain_error(
+"Several files in pkgsrc must contain the CVS Id, so that their current",
+"version can be traced back later from a binary package. This is to",
+"ensure reproducible builds, for example for finding bugs.",
+"",
+"Please insert the text from the above error message (without the quotes)",
+"at this position in the file.");
+ return false;
+ }
+ return true;
+}
+
+sub checkline_rcsid($$) {
+ my ($line, $prefix) = @_;
+
+ checkline_rcsid_regex($line, quotemeta($prefix), $prefix);
+}
+
+sub checkline_mk_absolute_pathname($$) {
+ my ($line, $text) = @_;
+ my $abspath;
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_absolute_pathname(${text})");
+
+ # In the GNU coding standards, DESTDIR is defined as a (usually
+ # empty) prefix that can be used to install files to a different
+ # location from what they have been built for. Therefore
+ # everything following it is considered an absolute pathname.
+ # Another commonly used context is in assignments like
+ # "bindir=/bin".
+ if ($text =~ m"(?:^|\$\{DESTDIR\}|\$\(DESTDIR\)|[\w_]+\s*=\s*)(/(?:[^\"'\`\s]|\"[^\"*]\"|'[^']*'|\`[^\`]*\`)*)") {
+ my $path = $1;
+
+ if ($path =~ m"^/\w") {
+ $abspath = $path;
+ }
+ }
+
+ if (defined($abspath)) {
+ checkword_absolute_pathname($line, $abspath);
+ }
+}
+
+sub checkline_relative_path($$$) {
+ my ($line, $path, $must_exist) = @_;
+ my ($res_path);
+
+ if (!$is_wip && $path =~ m"/wip/") {
+ $line->log_error("A pkgsrc package must not depend on any outside package.");
+ }
+ $res_path = resolve_relative_path($path, true);
+ if ($res_path =~ regex_unresolved) {
+ $opt_debug_unchecked and $line->log_debug("Unchecked path: \"${path}\".");
+ } elsif (!-e ((($res_path =~ m"^/") ? "" : "${current_dir}/") . $res_path)) {
+ $must_exist and $line->log_error("\"${res_path}\" does not exist.");
+ } elsif ($path =~ m"^\.\./\.\./([^/]+)/([^/]+)(.*)") {
+ my ($cat, $pkg, $rest) = ($1, $2, $3);
+ } elsif ($path =~ m"^\.\./\.\./mk/") {
+ # There need not be two directory levels for mk/ files.
+ } elsif ($path =~ m"^\.\./mk/" && $cur_pkgsrcdir eq "..") {
+ # That's fine for category Makefiles.
+ } elsif ($path =~ m"^\.\.") {
+ $line->log_warning("Invalid relative path \"${path}\".");
+ }
+}
+
+sub checkline_relative_pkgdir($$) {
+ my ($line, $path) = @_;
+
+ checkline_relative_path($line, $path, true);
+ $path = resolve_relative_path($path, false);
+
+ if ($path =~ m"^(?:\./)?\.\./\.\./([^/]+/[^/]+)$") {
+ my $otherpkgpath = $1;
+ if (! -f "$cwd_pkgsrcdir/$otherpkgpath/Makefile") {
+ $line->log_error("There is no package in $otherpkgpath.");
+ }
+
+ } else {
+ $line->log_warning("\"${path}\" is not a valid relative package directory.");
+ $line->explain_warning(
+"A relative pathname always starts with \"../../\", followed",
+"by a category, a slash and a the directory name of the package.",
+"For example, \"../../misc/screen\" is a valid relative pathname.");
+ }
+}
+
+sub checkline_mk_varuse($$$$) {
+ my ($line, $varname, $mod, $context) = @_;
+
+ assert(defined($varname), "The varname parameter must be defined");
+ assert(defined($context), "The context parameter must be defined");
+ $opt_debug_trace and $line->log_debug("checkline_mk_varuse(\"${varname}\", \"${mod}\", ".$context->to_string().")");
+
+ # Check for spelling mistakes.
+ my $type = get_variable_type($line, $varname);
+ if (defined($type) && !($type->is_guessed)) {
+ # Great.
+
+ } elsif (var_is_used($varname)) {
+ # Fine.
+
+ } elsif (defined($mkctx_for_variables) && exists($mkctx_for_variables->{$varname})) {
+ # Variables defined in .for loops are also ok.
+
+ } else {
+ $opt_warn_extra and $line->log_warning("${varname} is used but not defined. Spelling mistake?");
+ }
+
+ if ($opt_warn_perm) {
+ my $perms = get_variable_perms($line, $varname);
+ my $is_load_time; # Will the variable be used at load time?
+ my $is_indirect; # Might the variable be used indirectly at load time,
+ # for example by assigning it to another variable
+ # which then gets evaluated?
+
+ # Don't warn about variables that are not recorded in the
+ # pkglint variable definition.
+ if (defined($context->type) && $context->type->is_guessed()) {
+ $is_load_time = false;
+
+ } elsif ($context->time == VUC_TIME_LOAD && $perms !~ m"p") {
+ $is_load_time = true;
+ $is_indirect = false;
+
+ } elsif (defined($context->type) && $context->type->perms_union() =~ m"p" && $perms !~ m"p") {
+ $is_load_time = true;
+ $is_indirect = true;
+
+ } else {
+ $is_load_time = false;
+ }
+
+ if ($is_load_time && !$is_indirect) {
+ $line->log_warning("${varname} should not be evaluated at load time.");
+ $line->explain_warning(
+"Many variables, especially lists of something, get their values",
+"incrementally. Therefore it is generally unsafe to rely on their value",
+"until it is clear that it will never change again. This point is",
+"reached when the whole package Makefile is loaded and execution of the",
+"shell commands starts, in some cases earlier.",
+"",
+"Additionally, when using the \":=\" operator, each \$\$ is replaced",
+"with a single \$, so variables that have references to shell variables",
+"or regular expressions are modified in a subtle way.");
+ }
+ if ($is_load_time && $is_indirect) {
+ $line->log_warning("${varname} should not be evaluated indirectly at load time.");
+ $line->explain_warning(
+"The variable on the left-hand side may be evaluated at load time, but",
+"the variable on the right-hand side may not. Due to this assignment, it",
+"might be used indirectly at load-time, when it is not guaranteed to be",
+"properly defined.");
+ }
+
+ if ($perms !~ m"p" && $perms !~ m"u") {
+ $line->log_warning("${varname} may not be used in this file.");
+ }
+ }
+
+ if ($varname eq "LOCALBASE" && !$is_internal) {
+ $line->log_warning("The LOCALBASE variable should not be used by packages.");
+ $line->explain_warning(
+# from jlam via private mail.
+"Currently, LOCALBASE is typically used in these cases:",
+"",
+"(1) To locate a file or directory from another package.",
+"(2) To refer to own files after installation.",
+"",
+"In the first case, the example is:",
+"",
+" STRLIST= \${LOCALBASE}/bin/strlist",
+" do-build:",
+" cd \${WRKSRC} && \${STRLIST} *.str",
+"",
+"This should really be:",
+"",
+" EVAL_PREFIX= STRLIST_PREFIX=strlist",
+" STRLIST= \${STRLIST_PREFIX}/bin/strlist",
+" do-build:",
+" cd \${WRKSRC} && \${STRLIST} *.str",
+"",
+"In the second case, the example is:",
+"",
+" CONFIGURE_ENV+= --with-datafiles=\${LOCALBASE}/share/battalion",
+"",
+"This should really be:",
+"",
+" CONFIGURE_ENV+= --with-datafiles=\${PREFIX}/share/battalion");
+ }
+
+ my $needs_quoting = variable_needs_quoting($line, $varname, $context);
+
+ if ($context->shellword == VUC_SHELLWORD_FOR) {
+ if (!defined($type)) {
+ # Cannot check anything here.
+
+ } elsif ($type->kind_of_list == LK_INTERNAL) {
+ # Fine.
+
+ } elsif ($needs_quoting == doesnt_matter || $needs_quoting == false) {
+ # Fine, these variables are assumed to not
+ # contain special characters.
+
+ } else {
+ $line->log_warning("The variable ${varname} should not be used in .for loops.");
+ $line->explain_warning(
+"The .for loop splits its argument at sequences of white-space, as",
+"opposed to all other places in make(1), which act like the shell.",
+"Therefore only variables that are specifically designed to match this",
+"requirement should be used here.");
+ }
+ }
+
+ if ($opt_warn_quoting && $context->shellword != VUC_SHELLWORD_UNKNOWN && $needs_quoting != dont_know) {
+
+ # In GNU configure scripts, a few variables need to be
+ # passed through the :M* operator before they reach the
+ # configure scripts.
+ my $need_mstar = false;
+ if ($varname =~ regex_gnu_configure_volatile_vars) {
+ # When we are not checking a package, but some other file,
+ # the :M* operator is needed for safety.
+ if (!defined($pkgctx_vardef) || exists($pkgctx_vardef->{"GNU_CONFIGURE"})) {
+ $need_mstar = true;
+ }
+ }
+
+ my $stripped_mod = ($mod =~ m"(.*?)(?::M\*)?(?::Q)?$") ? $1 : $mod;
+ my $correct_mod = $stripped_mod . ($need_mstar ? ":M*:Q" : ":Q");
+
+ if ($mod eq ":M*:Q" && !$need_mstar) {
+ $line->log_note("The :M* modifier is not needed here.");
+
+ } elsif ($mod ne $correct_mod && $needs_quoting == true) {
+ if ($context->shellword == VUC_SHELLWORD_PLAIN) {
+ $line->log_warning("Please use \${${varname}${correct_mod}} instead of \${${varname}${mod}}.");
+ #$line->replace("\${${varname}}", "\${${varname}:Q}");
+ } else {
+ $line->log_warning("Please use \${${varname}${correct_mod}} instead of \${${varname}${mod}} and make sure the variable appears outside of any quoting characters.");
+ }
+ $line->explain_warning("See the pkgsrc guide, section \"quoting guideline\", for details.");
+ }
+
+ if ($mod =~ m":Q$") {
+ my @expl = (
+"Many variables in pkgsrc do not need the :Q operator, since they",
+"are not expected to contain white-space or other evil characters.",
+"",
+"Another case is when a variable of type ShellWord appears in a context",
+"that expects a shell word, it does not need to have a :Q operator. Even",
+"when it is concatenated with another variable, it still stays _one_ word.",
+"",
+"Example:",
+"\tWORD1= Have\\ fun # 1 word",
+"\tWORD2= \"with BSD Make\" # 1 word, too",
+"",
+"\tdemo:",
+"\t\techo \${WORD1}\${WORD2} # still 1 word");
+
+ if ($needs_quoting == false) {
+ $line->log_warning("The :Q operator should not be used for \${${varname}} here.");
+ $line->explain_warning(@expl);
+ } elsif ($needs_quoting == doesnt_matter) {
+ $line->log_note("The :Q operator isn't necessary for \${${varname}} here.");
+ $line->explain_note(@expl);
+ }
+ }
+ }
+
+ assert(defined($mkctx_build_defs), "The build_defs variable must be defined here.");
+ if (exists(get_userdefined_variables()->{$varname}) && !exists(get_system_build_defs()->{$varname}) && !exists($mkctx_build_defs->{$varname})) {
+ $line->log_warning("The user-defined variable ${varname} is used but not added to BUILD_DEFS.");
+ $line->explain_warning(
+"When a pkgsrc package is built, many things can be configured by the",
+"pkgsrc user in the mk.conf file. All these configurations should be",
+"recorded in the binary package, so the package can be reliably rebuilt.",
+"The BUILD_DEFS variable contains a list of all these user-settable",
+"variables, so please add your variable to it, too.");
+ }
+}
+
+sub checkline_mk_text($$) {
+ my ($line, $text) = @_;
+ my ($rest, $state, $vartools, $depr_map);
+
+ if ($text =~ m"^(?:[^#]*[^\$])?\$(\w+)") {
+ my ($varname) = ($1);
+ $line->log_warning("\$$varname is ambiguous. Use \${$varname} if you mean a Makefile variable or \$\$$varname if you mean a shell variable.");
+ }
+
+ if ($line->lines eq "1") {
+ checkline_rcsid_regex($line, qr"#\s+", "# ");
+ }
+
+ if ($text =~ m"\$\{WRKSRC\}/\.\./") {
+ $line->log_warning("Using \"\${WRKSRC}/..\" is conceptually wrong. Please use a combination of WRKSRC, CONFIGURE_DIRS and BUILD_DIRS instead.");
+ $line->explain_warning(
+"You should define WRKSRC such that all of CONFIGURE_DIRS, BUILD_DIRS",
+"and INSTALL_DIRS are subdirectories of it.");
+ }
+
+ if ($text =~ m"\b(-Wl,--rpath,|-Wl,-rpath-link,|-Wl,-rpath,|-Wl,-R)\b") {
+ $line->log_warning("Please use \${COMPILER_RPATH_FLAG} instead of $1.");
+ }
+ # Note: A simple -R is not detected, as the rate of false
+ # positives is too high.
+
+ $rest = $text;
+ $depr_map = get_deprecated_map();
+ while ($rest =~ s/(?:^|[^\$])\$\{([-A-Z0-9a-z_]+)(\.[\-0-9A-Z_a-z]+)?(?::[^\}]+)?\}//) {
+ my ($varbase, $varext) = ($1, $2);
+ my $varname = $varbase . (defined($varext) ? $varext : "");
+ my $varcanon = varname_canon($varname);
+ my $instead =
+ (exists($depr_map->{$varname})) ? $depr_map->{$varname}
+ : (exists($depr_map->{$varcanon})) ? $depr_map->{$varcanon}
+ : undef;
+
+ if (defined($instead)) {
+ $line->log_warning("Use of ${varname} is deprecated. ${instead}");
+ }
+ }
+
+ # Don't enforce purely aesthetic changes before more correct behaviour is implemented:
+ # $rest = $text;
+ # while ($rest =~ s/(?:^|[^\$])\$\(([-A-Z0-9a-z_]+)(?::[^\}]+)?\)//) {
+ # my ($varname) = ($1);
+
+ # $line->log_warning("Please use \${${varname}\} instead of \$(${varname}).");
+ # }
+
+}
+
+#include PkgLint/Shell.pm
+
+sub expand_permission($) {
+ my ($perm) = @_;
+ my %fullperm = ( "a" => "append", "d" => "default", "p" => "preprocess", "s" => "set", "u" => "runtime", "?" => "unknown" );
+ my $result = join(", ", map { $fullperm{$_} } split //, $perm);
+ $result =~ s/, $//g;
+
+ return $result;
+}
+
+sub checkline_mk_vardef($$$) {
+ my ($line, $varname, $op) = @_;
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_vardef(${varname}, ${op})");
+
+ # If we are checking a whole package, add it to the package-wide
+ # list of defined variables.
+ if (defined($pkgctx_vardef) && !exists($pkgctx_vardef->{$varname})) {
+ $pkgctx_vardef->{$varname} = $line;
+ }
+
+ # Add it to the file-wide list of defined variables.
+ if (!exists($mkctx_vardef->{$varname})) {
+ $mkctx_vardef->{$varname} = $line;
+ }
+
+ return unless $opt_warn_perm;
+
+ my $perms = get_variable_perms($line, $varname);
+ my $needed = { "=" => "s", "!=" => "s", "?=" => "d", "+=" => "a", ":=" => "s" }->{$op};
+ if (index($perms, $needed) == -1) {
+ $line->log_warning("Permission [" . expand_permission($needed) . "] requested for ${varname}, but only [" . expand_permission($perms) . "] is allowed.");
+ $line->explain_warning(
+"The available permissions are:",
+"\tappend\t\tappend something using +=",
+"\tdefault\t\tset a default value using ?=",
+"\tpreprocess\tuse a variable during preprocessing",
+"\truntime\t\tuse a variable at runtime",
+"\tset\t\tset a variable using :=, =, !=",
+"",
+"A \"?\" means that it is not yet clear which permissions are allowed",
+"and which aren't.");
+ }
+}
+
+# @param $op
+# The operator that is used for reading or writing to the variable.
+# One of: "=", "+=", ":=", "!=", "?=", "use", "pp-use", "".
+# For some variables (like BuildlinkDepth or BuildlinkPackages), the
+# operator influences the valid values.
+# @param $comment
+# In assignments, a part of the line may be commented out. If there
+# is no comment, pass C<undef>.
+#
+sub checkline_mk_vartype_basic($$$$$$$$);
+sub checkline_mk_vartype_basic($$$$$$$$) {
+ my ($line, $varname, $type, $op, $value, $comment, $list_context, $is_guessed) = @_;
+ my ($value_novar);
+
+ $opt_debug_trace and $line->log_debug(sprintf("checkline_mk_vartype_basic(%s, %s, %s, %s, %s, %s, %s)",
+ $varname, $type, $op, $value, defined($comment) ? $comment : "<undef>", $list_context, $is_guessed));
+
+ $value_novar = $value;
+ while ($value_novar =~ s/\$\{([^{}]*)\}//g) {
+ my ($varuse) = ($1);
+ if (!$list_context && $varuse =~ m":Q$") {
+ $line->log_warning("The :Q operator should only be used in lists and shell commands.");
+ }
+ }
+
+ my %type_dispatch = (
+ AwkCommand => sub {
+ $opt_debug_unchecked and $line->log_debug("Unchecked AWK command: ${value}");
+ },
+
+ BrokenIn => sub {
+ if ($value ne $value_novar) {
+ $line->log_error("${varname} must not refer to other variables.");
+
+ } elsif ($value =~ m"^pkgsrc-(\d\d\d\d)Q(\d)$") {
+ my ($year, $quarter) = ($1, $2);
+
+ # Fine.
+
+ } else {
+ $line->log_warning("Invalid value \"${value}\" for ${varname}.");
+ }
+ $line->log_note("Please remove this line if the package builds for you.");
+ },
+
+ BuildlinkDepmethod => sub {
+ # Note: this cannot be replaced with { build full } because
+ # enumerations may not contain references to other variables.
+ if ($value ne $value_novar) {
+ # No checks yet.
+ } elsif ($value ne "build" && $value ne "full") {
+ $line->log_warning("Invalid dependency method \"${value}\". Valid methods are \"build\" or \"full\".");
+ }
+ },
+
+ BuildlinkDepth => sub {
+ if (!($op eq "use" && $value eq "+")
+ && $value ne "\${BUILDLINK_DEPTH}+"
+ && $value ne "\${BUILDLINK_DEPTH:S/+\$//}") {
+ $line->log_warning("Invalid value for ${varname}.");
+ }
+ },
+
+ BuildlinkPackages => sub {
+ my $re_del = qr"\$\{BUILDLINK_PACKAGES:N(?:[+\-.0-9A-Z_a-z]|\$\{[^\}]+\})+\}";
+ my $re_add = qr"(?:[+\-.0-9A-Z_a-z]|\$\{[^\}]+\})+";
+
+ if (($op eq ":=" && $value =~ m"^${re_del}$") ||
+ ($op eq ":=" && $value =~ m"^${re_del}\s+${re_add}$") ||
+ ($op eq "+=" && $value =~ m"^${re_add}$")) {
+ # Fine.
+
+ } else {
+ $line->log_warning("Invalid value for ${varname}.");
+ }
+ },
+
+ Category => sub {
+ my $allowed_categories = join("|", qw(
+ archivers audio
+ benchmarks biology
+ cad chat chinese comms converters cross crosspkgtools
+ databases devel
+ editors emulators
+ filesystems finance fonts
+ games geography gnome gnustep graphics
+ ham
+ inputmethod
+ japanese java
+ kde korean
+ lang linux local
+ mail math mbone meta-pkgs misc multimedia
+ net news
+ packages parallel perl5 pkgtools plan9 print python
+ ruby
+ scm security shells sysutils
+ tcl textproc time tk
+ windowmaker wm www
+ x11 xmms
+ ));
+ if ($value !~ m"^(?:${allowed_categories})$") {
+ $line->log_error("Invalid category \"${value}\".");
+ }
+ },
+
+ CFlag => sub {
+ if ($value =~ m"^-D([0-9A-Z_a-z]+)=(.*)") {
+ my ($macname, $macval) = ($1, $2);
+
+ # No checks needed, since the macro definitions
+ # are usually directory names, which don't need
+ # any quoting.
+
+ } elsif ($value =~ m"^-[DU]([0-9A-Z_a-z]+)") {
+ my ($macname) = ($1);
+
+ $opt_debug_unchecked and $line->log_debug("Unchecked macro ${macname} in ${varname}.");
+
+ } elsif ($value =~ m"^-I(.*)") {
+ my ($dirname) = ($1);
+
+ $opt_debug_unchecked and $line->log_debug("Unchecked directory ${dirname} in ${varname}.");
+
+ } elsif ($value eq "-c99") {
+ # Only works on IRIX, but is usually enclosed with
+ # the proper preprocessor conditional.
+
+ } elsif ($value =~ m"^-[OWfgm]|^-std=.*") {
+ $opt_debug_unchecked and $line->log_debug("Unchecked compiler flag ${value} in ${varname}.");
+
+ } elsif ($value =~ m"^-.*") {
+ $line->log_warning("Unknown compiler flag \"${value}\".");
+
+ } elsif ($value =~ regex_unresolved) {
+ $opt_debug_unchecked and $line->log_debug("Unchecked CFLAG: ${value}");
+
+ } else {
+ $line->log_warning("Compiler flag \"${value}\" does not start with a dash.");
+ }
+ },
+
+ Comment => sub {
+ if ($value eq "SHORT_DESCRIPTION_OF_THE_PACKAGE") {
+ $line->log_error("COMMENT must be set.");
+ }
+ if ($value =~ m"^(a|an)\s+"i) {
+ $line->log_warning("COMMENT should not begin with '$1'.");
+ }
+ if ($value =~ m"^[a-z]") {
+ $line->log_warning("COMMENT should start with a capital letter.");
+ }
+ if ($value =~ m"\.$") {
+ $line->log_warning("COMMENT should not end with a period.");
+ }
+ if (length($value) > 70) {
+ $line->log_warning("COMMENT should not be longer than 70 characters.");
+ }
+ },
+
+ Dependency => sub {
+ if ($value =~ m"^(${regex_pkgbase})(<|=|>|<=|>=|!=|-)(${regex_pkgversion})$") {
+ my ($depbase, $depop, $depversion) = ($1, $2, $3);
+
+ } elsif ($value =~ m"^(${regex_pkgbase})-(?:\[(.*)\]\*|(\d+(?:\.\d+)*(?:\.\*)?)(\{,nb\*\}|\*|)|(.*))?$") {
+ my ($depbase, $bracket, $version, $version_wildcard, $other) = ($1, $2, $3, $4, $5);
+
+ if (defined($bracket)) {
+ if ($bracket ne "0-9") {
+ $line->log_warning("Only [0-9]* is allowed in the numeric part of a dependency.");
+ }
+
+ } elsif (defined($version) && defined($version_wildcard) && $version_wildcard ne "") {
+ # Great.
+
+ } elsif (defined($version)) {
+ $line->log_warning("Please append {,nb*} to the version number of this dependency.");
+ $line->explain_warning(
+"Usually, a dependency should stay valid when the PKGREVISION is",
+"increased, since those changes are most often editorial. In the",
+"current form, the dependency only matches if the PKGREVISION is",
+"undefined.");
+
+ } elsif ($other eq "*") {
+ $line->log_warning("Please use ${depbase}-[0-9]* instead of ${depbase}-*.");
+ $line->explain_warning(
+"If you use a * alone, the package specification may match other",
+"packages that have the same prefix, but a longer name. For example,",
+"foo-* matches foo-1.2, but also foo-client-1.2 and foo-server-1.2.");
+
+ } else {
+ $line->log_error("Unknown dependency pattern \"${value}\".");
+ }
+
+ } elsif ($value =~ m"\{") {
+ # Dependency patterns containing alternatives
+ # are just too hard to check.
+ $opt_debug_unchecked and $line->log_debug("Unchecked dependency pattern: ${value}");
+
+ } elsif ($value ne $value_novar) {
+ $opt_debug_unchecked and $line->log_debug("Unchecked dependency: ${value}");
+
+ } else {
+ $line->log_warning("Unknown dependency format: ${value}");
+ $line->explain_warning(
+"Typical dependencies have the form \"package>=2.5\", \"package-[0-9]*\"",
+"or \"package-3.141\".");
+ }
+ },
+
+ DependencyWithPath => sub {
+ if ($value =~ regex_unresolved) {
+ # don't even try to check anything
+ } elsif ($value =~ m"(.*):(\.\./\.\./([^/]+)/([^/]+))$") {
+ my ($pattern, $relpath, $cat, $pkg) = ($1, $2, $3, $4);
+
+ checkline_relative_pkgdir($line, $relpath);
+
+ if ($pkg eq "msgfmt" || $pkg eq "gettext") {
+ $line->log_warning("Please use USE_TOOLS+=msgfmt instead of this dependency.");
+
+ } elsif ($pkg =~ m"^perl\d+") {
+ $line->log_warning("Please use USE_TOOLS+=perl:run instead of this dependency.");
+
+ } elsif ($pkg eq "gmake") {
+ $line->log_warning("Please use USE_TOOLS+=gmake instead of this dependency.");
+
+ }
+
+ if ($pattern =~ regex_dependency_lge) {
+# ($abi_pkg, $abi_version) = ($1, $2);
+ } elsif ($pattern =~ regex_dependency_wildcard) {
+# ($abi_pkg) = ($1);
+ } else {
+ $line->log_error("Unknown dependency pattern \"${pattern}\".");
+ }
+
+ } elsif ($value =~ m":\.\./[^/]+$") {
+ $line->log_warning("Dependencies should have the form \"../../category/package\".");
+ $line->explain_warning(expl_relative_dirs);
+
+ } else {
+ $line->log_warning("Unknown dependency format.");
+ $line->explain_warning(
+"Examples for valid dependencies are:",
+" package-[0-9]*:../../category/package",
+" package>=3.41:../../category/package",
+" package-2.718:../../category/package");
+ }
+ },
+
+ DistSuffix => sub {
+ if ($value eq ".tar.gz") {
+ $line->log_note("${varname} is \".tar.gz\" by default, so this definition may be redundant.");
+ }
+ },
+
+ EmulPlatform => sub {
+ if ($value =~ m"^(\w+)-(\w+)$") {
+ my ($opsys, $arch) = ($1, $2);
+
+ if ($opsys !~ m"^(?:bsdos|cygwin|darwin|dragonfly|freebsd|haiku|hpux|interix|irix|linux|netbsd|openbsd|osf1|sunos|solaris)$") {
+ $line->log_warning("Unknown operating system: ${opsys}");
+ }
+ # no check for $os_version
+ if ($arch !~ m"^(?:i386|alpha|amd64|arc|arm|arm32|cobalt|convex|dreamcast|hpcmips|hpcsh|hppa|ia64|m68k|m88k|mips|mips64|mipsel|mipseb|mipsn32|ns32k|pc532|pmax|powerpc|rs6000|s390|sparc|sparc64|vax|x86_64)$") {
+ $line->log_warning("Unknown hardware architecture: ${arch}");
+ }
+
+ } else {
+ $line->log_warning("\"${value}\" is not a valid emulation platform.");
+ $line->explain_warning(
+"An emulation platform has the form <OPSYS>-<MACHINE_ARCH>.",
+"OPSYS is the lower-case name of the operating system, and MACHINE_ARCH",
+"is the hardware architecture.",
+"",
+"Examples: linux-i386, irix-mipsel.");
+ }
+ },
+
+ FetchURL => sub {
+ checkline_mk_vartype_basic($line, $varname, "URL", $op, $value, $comment, $list_context, $is_guessed);
+
+ my $sites = get_dist_sites();
+ foreach my $site (keys(%{$sites})) {
+ if (index($value, $site) == 0) {
+ my $subdir = substr($value, length($site));
+ my $is_github = $value =~ m"^https://github\.com/";
+ if ($is_github) {
+ $subdir =~ s|/.*|/|;
+ }
+ $line->log_warning(sprintf("Please use \${%s:=%s} instead of \"%s\".", $sites->{$site}, $subdir, $value));
+ if ($is_github) {
+ $line->log_warning("Run \"".conf_make." help topic=github\" for further tips.");
+ }
+ last;
+ }
+ }
+ },
+
+ Filename => sub {
+ if ($value_novar =~ m"/") {
+ $line->log_warning("A filename should not contain a slash.");
+
+ } elsif ($value_novar !~ m"^[-0-9\@A-Za-z.,_~+%]*$") {
+ $line->log_warning("\"${value}\" is not a valid filename.");
+ }
+ },
+
+ Filemask => sub {
+ if ($value_novar !~ m"^[-0-9A-Za-z._~+%*?]*$") {
+ $line->log_warning("\"${value}\" is not a valid filename mask.");
+ }
+ },
+
+ FileMode => sub {
+ if ($value ne "" && $value_novar eq "") {
+ # Fine.
+ } elsif ($value =~ m"^[0-7]{3,4}") {
+ # Fine.
+ } else {
+ $line->log_warning("Invalid file mode ${value}.");
+ }
+ },
+
+ Identifier => sub {
+ if ($value ne $value_novar) {
+ #$line->log_warning("Identifiers should be given directly.");
+ }
+ if ($value_novar =~ m"^[+\-.0-9A-Z_a-z]+$") {
+ # Fine.
+ } elsif ($value ne "" && $value_novar eq "") {
+ # Don't warn here.
+ } else {
+ $line->log_warning("Invalid identifier \"${value}\".");
+ }
+ },
+
+ Integer => sub {
+ if ($value !~ m"^\d+$") {
+ $line->log_warning("${varname} must be a valid integer.");
+ }
+ },
+
+ LdFlag => sub {
+ if ($value =~ m"^-L(.*)") {
+ my ($dirname) = ($1);
+
+ $opt_debug_unchecked and $line->log_debug("Unchecked directory ${dirname} in ${varname}.");
+
+ } elsif ($value =~ m"^-l(.*)") {
+ my ($libname) = ($1);
+
+ $opt_debug_unchecked and $line->log_debug("Unchecked library name ${libname} in ${varname}.");
+
+ } elsif ($value =~ m"^(?:-static)$") {
+ # Assume that the wrapper framework catches these.
+
+ } elsif ($value =~ m"^(-Wl,(?:-R|-rpath|--rpath))") {
+ my ($rpath_flag) = ($1);
+ $line->log_warning("Please use \${COMPILER_RPATH_FLAG} instead of ${rpath_flag}.");
+
+ } elsif ($value =~ m"^-.*") {
+ $line->log_warning("Unknown linker flag \"${value}\".");
+
+ } elsif ($value =~ regex_unresolved) {
+ $opt_debug_unchecked and $line->log_debug("Unchecked LDFLAG: ${value}");
+
+ } else {
+ $line->log_warning("Linker flag \"${value}\" does not start with a dash.");
+ }
+ },
+
+ License => sub {
+
+ use constant deprecated_licenses => array_to_hash(qw(
+ fee-based-commercial-use
+ no-commercial-use no-profit no-redistribution
+ shareware
+ ));
+
+ my $licenses = parse_licenses($value);
+ foreach my $license (@$licenses) {
+ my $license_file = "${cwd_pkgsrcdir}/licenses/${license}";
+ if (defined($pkgctx_vardef) && exists($pkgctx_vardef->{"LICENSE_FILE"})) {
+ my $license_file_line = $pkgctx_vardef->{"LICENSE_FILE"};
+
+ $license_file = "${current_dir}/" . resolve_relative_path($license_file_line->get("value"), false);
+ } else {
+ $ipc_used_licenses{$license} = true;
+ }
+
+ if (!-f $license_file) {
+ $line->log_warning("License file ".normalize_pathname($license_file)." does not exist.");
+ }
+
+ if (exists(deprecated_licenses->{$license})) {
+ $line->log_warning("License ${license} is deprecated.");
+ }
+ }
+ },
+
+ Mail_Address => sub {
+ if ($value =~ m"^([+\-.0-9A-Z_a-z]+)\@([-\w\d.]+)$") {
+ my ($localpart, $domain) = ($1, $2);
+ if ($domain =~ m"^NetBSD.org"i && $domain ne "NetBSD.org") {
+ $line->log_warning("Please write NetBSD.org instead of ${domain}.");
+ }
+ if ("${localpart}\@${domain}" =~ m"^(tech-pkg|packages)\@NetBSD\.org$"i) {
+ $line->log_warning("${localpart}\@${domain} is deprecated. Use pkgsrc-users\@NetBSD.org instead.");
+ }
+
+ } else {
+ $line->log_warning("\"${value}\" is not a valid mail address.");
+ }
+ },
+
+ Message => sub {
+ if ($value =~ m"^[\"'].*[\"']$") {
+ $line->log_warning("${varname} should not be quoted.");
+ $line->explain_warning(
+"The quoting is only needed for variables which are interpreted as",
+"multiple words (or, generally speaking, a list of something). A single",
+"text message does not belong to this class, since it is only printed",
+"as a whole.",
+"",
+"On the other hand, PKG_FAIL_REASON is a _list_ of text messages, so in",
+"that case, the quoting has to be done.");
+ }
+ },
+
+ Option => sub {
+ if ($value ne $value_novar) {
+ $opt_debug_unchecked and $line->log_debug("Unchecked option name \"${value}\".");
+
+ } elsif ($value_novar =~ m"^-?([a-z][-0-9a-z\+]*)$") {
+ my ($optname) = ($1);
+
+ if (!exists(get_pkg_options()->{$optname})) {
+ $line->log_warning("Unknown option \"${value}\".");
+ $line->explain_warning(
+"This option is not documented in the mk/defaults/options.description",
+"file. If this is not a typo, please think of a brief but precise",
+"description and either update that file yourself or ask on the",
+"tech-pkg\@NetBSD.org mailing list.");
+ }
+
+ } elsif ($value_novar =~ m"^-?([a-z][-0-9a-z_\+]*)$") {
+ my ($optname) = ($1);
+
+ $line->log_warning("Use of the underscore character in option names is deprecated.");
+
+ } else {
+ $line->log_error("\"${value}\" is not a valid option name.");
+ }
+ },
+
+ Pathlist => sub {
+
+ if ($value !~ m":" && $is_guessed) {
+ checkline_mk_vartype_basic($line, $varname, "Pathname", $op, $value, $comment, $list_context, $is_guessed);
+
+ } else {
+
+ # XXX: The splitting will fail if $value contains any
+ # variables with modifiers, for example :Q or :S/././.
+ foreach my $p (split(qr":", $value)) {
+ my $p_novar = remove_variables($p);
+
+ if ($p_novar !~ m"^[-0-9A-Za-z._~+%/]*$") {
+ $line->log_warning("\"${p}\" is not a valid pathname.");
+ }
+
+ if ($p !~ m"^[\$/]") {
+ $line->log_warning("All components of ${varname} (in this case \"${p}\") should be an absolute path.");
+ }
+ }
+ }
+ },
+
+ Pathmask => sub {
+ if ($value_novar !~ m"^[#\-0-9A-Za-z._~+%*?/\[\]]*$") {
+ $line->log_warning("\"${value}\" is not a valid pathname mask.");
+ }
+ checkline_mk_absolute_pathname($line, $value);
+ },
+
+ Pathname => sub {
+ if ($value_novar !~ m"^[#\-0-9A-Za-z._~+%/]*$") {
+ $line->log_warning("\"${value}\" is not a valid pathname.");
+ }
+ checkline_mk_absolute_pathname($line, $value);
+ },
+
+ Perl5Packlist => sub {
+ if ($value ne $value_novar) {
+ $line->log_warning("${varname} should not depend on other variables.");
+ }
+ },
+
+ PkgName => sub {
+ if ($value eq $value_novar && $value !~ regex_pkgname) {
+ $line->log_warning("\"${value}\" is not a valid package name. A valid package name has the form packagename-version, where version consists only of digits, letters and dots.");
+ }
+ },
+
+ PkgPath => sub {
+ checkline_relative_pkgdir($line, "$cur_pkgsrcdir/$value");
+ },
+
+ PkgOptionsVar => sub {
+ checkline_mk_vartype_basic($line, $varname, "Varname", $op, $value, $comment, false, $is_guessed);
+ if ($value =~ m"\$\{PKGBASE[:\}]") {
+ $line->log_error("PKGBASE must not be used in PKG_OPTIONS_VAR.");
+ $line->explain_error(
+"PKGBASE is defined in bsd.pkg.mk, which is included as the",
+"very last file, but PKG_OPTIONS_VAR is evaluated earlier.",
+"Use \${PKGNAME:C/-[0-9].*//} instead.");
+ }
+ },
+
+ PkgRevision => sub {
+ if ($value !~ m"^[1-9]\d*$") {
+ $line->log_warning("${varname} must be a positive integer number.");
+ }
+ if ($line->fname !~ m"(?:^|/)Makefile$") {
+ $line->log_error("${varname} only makes sense directly in the package Makefile.");
+ $line->explain_error(
+"Usually, different packages using the same Makefile.common have",
+"different dependencies and will be bumped at different times (e.g. for",
+"shlib major bumps) and thus the PKGREVISIONs must be in the separate",
+"Makefiles. There is no practical way of having this information in a",
+"commonly used Makefile.");
+ }
+ },
+
+ PlatformTriple => sub {
+ my $part = qr"(?:\[[^\]]+\]|[^-\[])+";
+ if ($value =~ m"^(${part})-(${part})-(${part})$") {
+ my ($opsys, $os_version, $arch) = ($1, $2, $3);
+
+ if ($opsys !~ m"^(?:\*|BSDOS|Cygwin|Darwin|DragonFly|FreeBSD|Haiku|HPUX|Interix|IRIX|Linux|NetBSD|OpenBSD|OSF1|QNX|SunOS)$") {
+ $line->log_warning("Unknown operating system: ${opsys}");
+ }
+ # no check for $os_version
+ if ($arch !~ m"^(?:\*|i386|alpha|amd64|arc|arm|arm32|cobalt|convex|dreamcast|hpcmips|hpcsh|hppa|ia64|m68k|m88k|mips|mips64|mipsel|mipseb|mipsn32|ns32k|pc532|pmax|powerpc|rs6000|s390|sparc|sparc64|vax|x86_64)$") {
+ $line->log_warning("Unknown hardware architecture: ${arch}");
+ }
+
+ } else {
+ $line->log_warning("\"${value}\" is not a valid platform triple.");
+ $line->explain_warning(
+"A platform triple has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
+"Each of these components may be a shell globbing expression.",
+"Examples: NetBSD-*-i386, *-*-*, Linux-*-*.");
+ }
+ },
+
+ PrefixPathname => sub {
+ if ($value =~ m"^man/(.*)") {
+ my ($mansubdir) = ($1);
+
+ $line->log_warning("Please use \"\${PKGMANDIR}/${mansubdir}\" instead of \"${value}\".");
+ }
+ },
+
+ PythonDependency => sub {
+ if ($value ne $value_novar) {
+ $line->log_warning("Python dependencies should not contain variables.");
+ }
+ if ($value_novar !~ m"^[+\-.0-9A-Z_a-z]+(?:|:link|:build)$") {
+ $line->log_warning("Invalid Python dependency \"${value}\".");
+ $line->explain_warning(
+"Python dependencies must be an identifier for a package, as specified",
+"in lang/python/versioned_dependencies.mk. This identifier may be",
+"followed by :build for a build-time only dependency, or by :link for",
+"a run-time only dependency.");
+ }
+ },
+
+ RelativePkgDir => sub {
+ checkline_relative_pkgdir($line, $value);
+ },
+
+ RelativePkgPath => sub {
+ checkline_relative_path($line, $value, true);
+ },
+
+ Restricted => sub {
+ if ($value ne "\${RESTRICTED}") {
+ $line->log_warning("The only valid value for ${varname} is \${RESTRICTED}.");
+ $line->explain_warning(
+"These variables are used to control which files may be mirrored on FTP",
+"servers or CD-ROM collections. They are not intended to mark packages",
+"whose only MASTER_SITES are on ftp.NetBSD.org.");
+ }
+ },
+
+ SedCommand => sub {
+ },
+
+ SedCommands => sub {
+ my $words = shell_split($value);
+ if (!$words) {
+ $line->log_error("Invalid shell words in sed commands.");
+ $line->explain_error(
+"If your sed commands have embedded \"#\" characters, you need to escape",
+"them with a backslash, otherwise make(1) will interpret them as a",
+"comment, no matter if they occur in single or double quotes or",
+"whatever.");
+
+ } else {
+ my $nwords = scalar(@{$words});
+ my $ncommands = 0;
+
+ for (my $i = 0; $i < $nwords; $i++) {
+ my $word = $words->[$i];
+ checkline_mk_shellword($line, $word, true);
+
+ if ($word eq "-e") {
+ if ($i + 1 < $nwords) {
+ # Check the real sed command here.
+ $i++;
+ $ncommands++;
+ if ($ncommands > 1) {
+ $line->log_warning("Each sed command should appear in an assignment of its own.");
+ $line->explain_warning(
+"For example, instead of",
+" SUBST_SED.foo+= -e s,command1,, -e s,command2,,",
+"use",
+" SUBST_SED.foo+= -e s,command1,,",
+" SUBST_SED.foo+= -e s,command2,,",
+"",
+"This way, short sed commands cannot be hidden at the end of a line.");
+ }
+ checkline_mk_shellword($line, $words->[$i - 1], true);
+ checkline_mk_shellword($line, $words->[$i], true);
+ checkline_mk_vartype_basic($line, $varname, "SedCommand", $op, $words->[$i], $comment, $list_context, $is_guessed);
+ } else {
+ $line->log_error("The -e option to sed requires an argument.");
+ }
+ } elsif ($word eq "-E") {
+ # Switch to extended regular expressions mode.
+
+ } elsif ($word eq "-n") {
+ # Don't print lines per default.
+
+ } elsif ($i == 0 && $word =~ m"^([\"']?)(?:\d*|/.*/)s(.).*\2g?\1$") {
+ $line->log_warning("Please always use \"-e\" in sed commands, even if there is only one substitution.");
+
+ } else {
+ $line->log_warning("Unknown sed command ${word}.");
+ }
+ }
+ }
+ },
+
+ ShellCommand => sub {
+ checkline_mk_shelltext($line, $value);
+ },
+
+ ShellWord => sub {
+ if (!$list_context) {
+ checkline_mk_shellword($line, $value, true);
+ }
+ },
+
+ Stage => sub {
+ if ($value !~ m"^(?:pre|do|post)-(?:extract|patch|configure|build|install)$") {
+ $line->log_warning("Invalid stage name. Use one of {pre,do,post}-{extract,patch,configure,build,install}.");
+ }
+ },
+
+ String => sub {
+ # No further checks possible.
+ },
+
+ Tool => sub {
+ if ($varname eq "TOOLS_NOOP" && $op eq "+=") {
+ # no warning for package-defined tool definitions
+
+ } elsif ($value =~ m"^([-\w]+|\[)(?::(\w+))?$") {
+ my ($toolname, $tooldep) = ($1, $2);
+ if (!exists(get_tool_names()->{$toolname})) {
+ $line->log_error("Unknown tool \"${toolname}\".");
+ }
+ if (defined($tooldep) && $tooldep !~ m"^(?:bootstrap|build|pkgsrc|run)$") {
+ $line->log_error("Unknown tool dependency \"${tooldep}\". Use one of \"build\", \"pkgsrc\" or \"run\".");
+ }
+ } else {
+ $line->log_error("Invalid tool syntax: \"${value}\".");
+ }
+ },
+
+ Unchecked => sub {
+ # Do nothing, as the name says.
+ },
+
+ URL => sub {
+ if ($value eq "" && defined($comment) && $comment =~ m"^#") {
+ # Ok
+
+ } elsif ($value =~ m"\$\{(MASTER_SITE_[^:]*).*:=(.*)\}$") {
+ my ($name, $subdir) = ($1, $2);
+
+ if (!exists(get_dist_sites_names()->{$name})) {
+ $line->log_error("${name} does not exist.");
+ }
+ if ($subdir !~ m"/$") {
+ $line->log_error("The subdirectory in ${name} must end with a slash.");
+ }
+
+ } elsif ($value =~ regex_unresolved) {
+ # No further checks
+
+ } elsif ($value =~ m"^(https?|ftp|gopher)://([-0-9A-Za-z.]+)(?::(\d+))?/([-%&+,./0-9:=?\@A-Z_a-z~]|#)*$") {
+ my ($proto, $host, $port, $path) = ($1, $2, $3, $4);
+
+ if ($host =~ m"\.NetBSD\.org$"i && $host !~ m"\.NetBSD\.org$") {
+ $line->log_warning("Please write NetBSD.org instead of ${host}.");
+ }
+
+ } elsif ($value =~ m"^([0-9A-Za-z]+)://([^/]+)(.*)$") {
+ my ($scheme, $host, $abs_path) = ($1, $2, $3);
+
+ if ($scheme ne "ftp" && $scheme ne "http" && $scheme ne "https" && $scheme ne "gopher") {
+ $line->log_warning("\"${value}\" is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.");
+
+ } elsif ($abs_path eq "") {
+ $line->log_note("For consistency, please add a trailing slash to \"${value}\".");
+
+ } else {
+ $line->log_warning("\"${value}\" is not a valid URL.");
+ }
+
+ } else {
+ $line->log_warning("\"${value}\" is not a valid URL.");
+ }
+ },
+
+ UserGroupName => sub {
+ if ($value ne $value_novar) {
+ # No checks for now.
+ } elsif ($value !~ m"^[0-9_a-z]+$") {
+ $line->log_warning("Invalid user or group name \"${value}\".");
+ }
+ },
+
+ Varname => sub {
+ if ($value ne "" && $value_novar eq "") {
+ # The value of another variable
+
+ } elsif ($value_novar !~ m"^[A-Z_][0-9A-Z_]*(?:[.].*)?$") {
+ $line->log_warning("\"${value}\" is not a valid variable name.");
+ }
+ },
+
+ Version => sub {
+ if ($value !~ m"^([\d.])+$") {
+ $line->log_warning("Invalid version number \"${value}\".");
+ }
+ },
+
+ WrapperReorder => sub {
+ if ($value =~ m"^reorder:l:([\w\-]+):([\w\-]+)$") {
+ my ($lib1, $lib2) = ($1, $2);
+ # Fine.
+ } else {
+ $line->log_warning("Unknown wrapper reorder command \"${value}\".");
+ }
+ },
+
+ WrapperTransform => sub {
+ if ($value =~ m"^rm:(?:-[DILOUWflm].*|-std=.*)$") {
+ # Fine.
+
+ } elsif ($value =~ m"^l:([^:]+):(.+)$") {
+ my ($lib, $replacement_libs) = ($1, $2);
+ # Fine.
+
+ } elsif ($value =~ m"^'?(?:opt|rename|rm-optarg|rmdir):.*$") {
+ # FIXME: This is cheated.
+ # Fine.
+
+ } elsif ($value eq "-e" || $value =~ m"^\"?'?s[|:,]") {
+ # FIXME: This is cheated.
+ # Fine.
+
+ } else {
+ $line->log_warning("Unknown wrapper transform command \"${value}\".");
+ }
+ },
+
+ WrkdirSubdirectory => sub {
+ checkline_mk_vartype_basic($line, $varname, "Pathname", $op, $value, $comment, $list_context, $is_guessed);
+ if ($value eq "\${WRKDIR}") {
+ # Fine.
+ } else {
+ $opt_debug_unchecked and $line->log_debug("Unchecked subdirectory \"${value}\" of \${WRKDIR}.");
+ }
+ },
+
+ WrksrcSubdirectory => sub {
+ if ($value =~ m"^(\$\{WRKSRC\})(?:/(.*))?") {
+ my ($prefix, $rest) = ($1, $2);
+ $line->log_note("You can use \"" . (defined($rest) ? $rest : ".") . "\" instead of \"${value}\".");
+
+ } elsif ($value ne "" && $value_novar eq "") {
+ # The value of another variable
+
+ } elsif ($value_novar !~ m"^(?:\.|[0-9A-Za-z_\@][-0-9A-Za-z_\@./+]*)$") {
+ $line->log_warning("\"${value}\" is not a valid subdirectory of \${WRKSRC}.");
+ }
+ },
+
+ Yes => sub {
+ if ($value !~ m"^(?:YES|yes)(?:\s+#.*)?$") {
+ $line->log_warning("${varname} should be set to YES or yes.");
+ $line->explain_warning(
+"This variable means \"yes\" if it is defined, and \"no\" if it is",
+"undefined. Even when it has the value \"no\", this means \"yes\".",
+"Therefore when it is defined, its value should correspond to its",
+"meaning.");
+ }
+ },
+
+ YesNo => sub {
+ if ($value !~ m"^(?:YES|yes|NO|no)(?:\s+#.*)?$") {
+ $line->log_warning("${varname} should be set to YES, yes, NO, or no.");
+ }
+ },
+
+ YesNo_Indirectly => sub {
+ if ($value_novar ne "" && $value !~ m"^(?:YES|yes|NO|no)(?:\s+#.*)?$") {
+ $line->log_warning("${varname} should be set to YES, yes, NO, or no.");
+ }
+ },
+ );
+
+ if (ref($type) eq "HASH") {
+ if (!exists($type->{$value})) {
+ $line->log_warning("\"${value}\" is not valid for ${varname}. Use one of { ".join(" ", sort(keys(%{$type})))." } instead.");
+ }
+
+ } elsif (defined $type_dispatch{$type}) {
+ $type_dispatch{$type}->();
+
+ } else {
+ $line->log_fatal("Type ${type} unknown.");
+ }
+}
+
+# Checks whether the list of version numbers that are given as the
+# C<value> of the variable C<varname> are in decreasing order.
+sub checkline_decreasing_order($$$) {
+ my ($line, $varname, $value) = @_;
+
+ my @pyver = split(qr"\s+", $value);
+ if (!@pyver) {
+ $line->log_error("There must be at least one value for ${varname}.");
+ return;
+ }
+
+ my $ver = shift(@pyver);
+ if ($ver !~ m"^\d+$") {
+ $line->log_error("All values for ${varname} must be numeric.");
+ return;
+ }
+
+ while (@pyver) {
+ my $nextver = shift(@pyver);
+ if ($nextver !~ m"^\d+$") {
+ $line->log_error("All values for ${varname} must be numeric.");
+ return;
+ }
+
+ if ($nextver >= $ver) {
+ $line->log_warning("The values for ${varname} should be in decreasing order.");
+ $line->explain_warning(
+"If they aren't, it may be possible that needless versions of packages",
+"are installed.");
+ }
+ $ver = $nextver;
+ }
+}
+
+sub checkline_mk_vartype($$$$$) {
+ my ($line, $varname, $op, $value, $comment) = @_;
+
+ return unless $opt_warn_types;
+
+ my $vartypes = get_vartypes_map();
+ my $varbase = varname_base($varname);
+ my $varcanon = varname_canon($varname);
+
+ my $type = get_variable_type($line, $varname);
+
+ if ($op eq "+=") {
+ if (defined($type)) {
+ if (!$type->may_use_plus_eq()) {
+ $line->log_warning("The \"+=\" operator should only be used with lists.");
+ }
+ } elsif ($varbase !~ m"^_" && $varbase !~ get_regex_plurals()) {
+ $line->log_warning("As ${varname} is modified using \"+=\", its name should indicate plural.");
+ }
+ }
+
+ if (!defined($type)) {
+ # Cannot check anything if the type is not known.
+ $opt_debug_unchecked and $line->log_debug("Unchecked variable assignment for ${varname}.");
+
+ } elsif ($op eq "!=") {
+ $opt_debug_misc and $line->log_debug("Use of !=: ${value}");
+
+ } elsif ($type->kind_of_list != LK_NONE) {
+ my (@words, $rest);
+
+ if ($type->kind_of_list == LK_INTERNAL) {
+ @words = split(qr"\s+", $value);
+ $rest = "";
+ } else {
+ @words = ();
+ $rest = $value;
+ while ($rest =~ s/^$regex_shellword//) {
+ my ($word) = ($1);
+ last if ($word =~ m"^#");
+ push(@words, $1);
+ }
+ }
+
+ foreach my $word (@words) {
+ checkline_mk_vartype_basic($line, $varname, $type->basic_type, $op, $word, $comment, true, $type->is_guessed);
+ if ($type->kind_of_list != LK_INTERNAL) {
+ checkline_mk_shellword($line, $word, true);
+ }
+ }
+
+ if ($rest !~ m"^\s*$") {
+ $line->log_error("Internal pkglint error: rest=${rest}");
+ }
+
+ } else {
+ checkline_mk_vartype_basic($line, $varname, $type->basic_type, $op, $value, $comment, $type->is_practically_a_list(), $type->is_guessed);
+ }
+}
+
+sub checkline_mk_varassign($$$$$) {
+ my ($line, $varname, $op, $value, $comment) = @_;
+ my ($used_vars);
+ my $varbase = varname_base($varname);
+ my $varcanon = varname_canon($varname);
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_varassign($varname, $op, $value)");
+
+ checkline_mk_vardef($line, $varname, $op);
+
+ if ($op eq "?=" && defined($seen_bsd_prefs_mk) && !$seen_bsd_prefs_mk) {
+ if ($varbase eq "BUILDLINK_PKGSRCDIR"
+ || $varbase eq "BUILDLINK_DEPMETHOD"
+ || $varbase eq "BUILDLINK_ABI_DEPENDS") {
+ # FIXME: What about these ones? They occur quite often.
+ } else {
+ $opt_warn_extra and $line->log_warning("Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".");
+ $opt_warn_extra and $line->explain_warning(
+"The ?= operator is used to provide a default value to a variable. In",
+"pkgsrc, many variables can be set by the pkgsrc user in the mk.conf",
+"file. This file must be included explicitly. If a ?= operator appears",
+"before mk.conf has been included, it will not care about the user's",
+"preferences, which can result in unexpected behavior. The easiest way",
+"to include the mk.conf file is by including the bsd.prefs.mk file,",
+"which will take care of everything.");
+ }
+ }
+
+ checkline_mk_text($line, $value);
+ checkline_mk_vartype($line, $varname, $op, $value, $comment);
+
+ # If the variable is not used and is untyped, it may be a
+ # spelling mistake.
+ if ($op eq ":=" && $varname eq lc($varname)) {
+ $opt_debug_unchecked and $line->log_debug("${varname} might be unused unless it is an argument to a procedure file.");
+ # TODO: check $varname against the list of "procedure files".
+
+ } elsif (!var_is_used($varname)) {
+ my $vartypes = get_vartypes_map();
+ my $deprecated = get_deprecated_map();
+
+ if (exists($vartypes->{$varname}) || exists($vartypes->{$varcanon})) {
+ # Ok
+ } elsif (exists($deprecated->{$varname}) || exists($deprecated->{$varcanon})) {
+ # Ok
+ } else {
+ $line->log_warning("${varname} is defined but not used. Spelling mistake?");
+ }
+ }
+
+ if ($value =~ m"/etc/rc\.d") {
+ $line->log_warning("Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to \${RCD_SCRIPTS_EXAMPLEDIR}.");
+ }
+
+ if (!$is_internal && $varname =~ m"^_") {
+ $line->log_warning("Variable names starting with an underscore are reserved for internal pkgsrc use.");
+ }
+
+ if ($varname eq "PERL5_PACKLIST" && defined($effective_pkgbase) && $effective_pkgbase =~ m"^p5-(.*)") {
+ my ($guess) = ($1);
+ $guess =~ s/-/\//g;
+ $guess = "auto/${guess}/.packlist";
+
+ my ($ucvalue, $ucguess) = (uc($value), uc($guess));
+ if ($ucvalue ne $ucguess && $ucvalue ne "\${PERL5_SITEARCH\}/${ucguess}") {
+ $line->log_warning("Unusual value for PERL5_PACKLIST -- \"${guess}\" expected.");
+ }
+ }
+
+ if ($varname eq "CONFIGURE_ARGS" && $value =~ m"=\$\{PREFIX\}/share/kde") {
+ $line->log_note("Please .include \"../../meta-pkgs/kde3/kde3.mk\" instead of this line.");
+ $line->explain_note(
+"That file probably does many things automatically and consistently that",
+"this package also does. When using kde3.mk, you can probably also leave",
+"out some explicit dependencies.");
+ }
+
+ if ($varname eq "EVAL_PREFIX" && $value =~ m"^([\w_]+)=") {
+ my ($eval_varname) = ($1);
+
+ # The variables mentioned in EVAL_PREFIX will later be
+ # defined by find-prefix.mk. Therefore, they are marked
+ # as known in the current file.
+ $mkctx_vardef->{$eval_varname} = $line;
+ }
+
+ if ($varname eq "PYTHON_VERSIONS_ACCEPTED") {
+ checkline_decreasing_order($line, $varname, $value);
+ }
+
+ if (defined($comment) && $comment eq "# defined" && $varname !~ m".*(?:_MK|_COMMON)$") {
+ $line->log_note("Please use \"# empty\", \"# none\" or \"yes\" instead of \"# defined\".");
+ $line->explain_note(
+"The value #defined says something about the state of the variable, but",
+"not what that _means_. In some cases a variable that is defined means",
+"\"yes\", in other cases it is an empty list (which is also only the",
+"state of the variable), whose meaning could be described with \"none\".",
+"It is this meaning that should be described.");
+ }
+
+ if ($value =~ m"\$\{(PKGNAME|PKGVERSION)[:\}]") {
+ my ($pkgvarname) = ($1);
+ if ($varname =~ m"^PKG_.*_REASON$") {
+ # ok
+ } elsif ($varname =~ m"^(?:DIST_SUBDIR|WRKSRC)$") {
+ $line->log_warning("${pkgvarname} should not be used in ${varname}, as it sometimes includes the PKGREVISION. Please use ${pkgvarname}_NOREV instead.");
+ } else {
+ $opt_debug_misc and $line->log_debug("Use of PKGNAME in ${varname}.");
+ }
+ }
+
+ if (exists(get_deprecated_map()->{$varname})) {
+ $line->log_warning("Definition of ${varname} is deprecated. ".get_deprecated_map()->{$varname});
+ } elsif (exists(get_deprecated_map()->{$varcanon})) {
+ $line->log_warning("Definition of ${varname} is deprecated. ".get_deprecated_map()->{$varcanon});
+ }
+
+ if ($varname =~ m"^SITES_") {
+ $line->log_warning("SITES_* is deprecated. Please use SITES.* instead.");
+ }
+
+ if ($value =~ m"^[^=]\@comment") {
+ $line->log_warning("Please don't use \@comment in ${varname}.");
+ $line->explain_warning(
+"Here you are defining a variable containing \@comment. As this value",
+"typically includes a space as the last character you probably also used",
+"quotes around the variable. This can lead to confusion when adding this",
+"variable to PLIST_SUBST, as all other variables are quoted using the :Q",
+"operator when they are appended. As it is hard to check whether a",
+"variable that is appended to PLIST_SUBST is already quoted or not, you",
+"should not have pre-quoted variables at all. To solve this, you should",
+"directly use PLIST_SUBST+= ${varname}=${value} or use any other",
+"variable for collecting the list of PLIST substitutions and later",
+"append that variable with PLIST_SUBST+= \${MY_PLIST_SUBST}.");
+ }
+
+ # Mark the variable as PLIST condition. This is later used in
+ # checkfile_PLIST.
+ if (defined($pkgctx_plist_subst_cond) && $value =~ m"(.+)=.*\@comment.*") {
+ $pkgctx_plist_subst_cond->{$1}++;
+ }
+
+ use constant op_to_use_time => {
+ ":=" => VUC_TIME_LOAD,
+ "!=" => VUC_TIME_LOAD,
+ "=" => VUC_TIME_RUN,
+ "+=" => VUC_TIME_RUN,
+ "?=" => VUC_TIME_RUN
+ };
+
+ $used_vars = extract_used_variables($line, $value);
+ my $vuc = PkgLint::VarUseContext->new(
+ op_to_use_time->{$op},
+ get_variable_type($line, $varname),
+ VUC_SHELLWORD_UNKNOWN, # XXX: maybe PLAIN?
+ VUC_EXTENT_UNKNOWN
+ );
+ foreach my $used_var (@{$used_vars}) {
+ checkline_mk_varuse($line, $used_var, "", $vuc);
+ }
+}
+
+# The bmake parser is way too sloppy about syntax, so we need to check
+# that here.
+#
+sub checkline_mk_cond($$) {
+ my ($line, $cond) = @_;
+ my ($op, $varname, $match, $value);
+
+ $opt_debug_trace and $line->log_debug("checkline_mk_cond($cond)");
+ my $tree = parse_mk_cond($line, $cond);
+ if (tree_match($tree, ["not", ["empty", ["match", \$varname, \$match]]])) {
+ #$line->log_note("tree_match: varname=$varname, match=$match");
+
+ my $type = get_variable_type($line, $varname);
+ my $btype = defined($type) ? $type->basic_type : undef;
+ if (defined($btype) && ref($type->basic_type) eq "HASH") {
+ if ($match !~ m"[\$\[*]" && !exists($btype->{$match})) {
+ $line->log_warning("Invalid :M value \"$match\". Only { " . join(" ", sort keys %$btype) . " } are allowed.");
+ }
+ }
+
+ # Currently disabled because the valid options can also be defined in PKG_OPTIONS_GROUP.*.
+ # Additionally, all these variables may have multiple assigments (+=).
+ if (false && $varname eq "PKG_OPTIONS" && defined($pkgctx_vardef) && exists($pkgctx_vardef->{"PKG_SUPPORTED_OPTIONS"})) {
+ my $options = $pkgctx_vardef->{"PKG_SUPPORTED_OPTIONS"}->get("value");
+
+ if ($match !~ m"[\$\[*]" && index(" $options ", " $match ") == -1) {
+ $line->log_warning("Invalid option \"$match\". Only { $options } are allowed.");
+ }
+ }
+
+ # TODO: PKG_BUILD_OPTIONS. That requires loading the
+ # complete package definitition for the package "pkgbase"
+ # or some other database. If we could confine all option
+ # definitions to options.mk, this would become easier.
+
+ } elsif (tree_match($tree, [\$op, ["var", \$varname], ["string", \$value]])) {
+ checkline_mk_vartype($line, $varname, "use", $value, undef);
+
+ }
+ # XXX: When adding new cases, be careful that the variables may have
+ # been partially initialized by previous calls to tree_match.
+ # XXX: Maybe it is better to clear these references at the beginning
+ # of tree_match.
+}
+
+#
+# Procedures to check an array of lines.
+#
+
+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_package_Makefile_varorder($) {
+ my ($lines) = @_;
+
+ return unless $opt_warn_varorder;
+
+ use enum qw(once optional many);
+ my (@sections) = (
+ [ "Initial comments", once,
+ [
+ ]
+ ],
+ [ "Unsorted stuff, part 1", once,
+ [
+ [ "DISTNAME", optional ],
+ [ "PKGNAME", optional ],
+ [ "PKGREVISION", optional ],
+ [ "CATEGORIES", once ],
+ [ "MASTER_SITES", optional ],
+ [ "DIST_SUBDIR", optional ],
+ [ "EXTRACT_SUFX", optional ],
+ [ "DISTFILES", many ],
+ [ "SITES.*", many ],
+ ]
+ ],
+ [ "Distribution patches", optional,
+ [
+ [ "PATCH_SITES", optional ], # or once?
+ [ "PATCH_SITE_SUBDIR", optional ],
+ [ "PATCHFILES", optional ], # or once?
+ [ "PATCH_DIST_ARGS", optional ],
+ [ "PATCH_DIST_STRIP", optional ],
+ [ "PATCH_DIST_CAT", optional ],
+ ]
+ ],
+ [ "Unsorted stuff, part 2", once,
+ [
+ [ "MAINTAINER", optional ],
+ [ "OWNER", optional ],
+ [ "HOMEPAGE", optional ],
+ [ "COMMENT", once ],
+ [ "LICENSE", once ],
+ ]
+ ],
+ [ "Legal issues", optional,
+ [
+ [ "LICENSE_FILE", optional ],
+ [ "RESTRICTED", optional ],
+ [ "NO_BIN_ON_CDROM", optional ],
+ [ "NO_BIN_ON_FTP", optional ],
+ [ "NO_SRC_ON_CDROM", optional ],
+ [ "NO_SRC_ON_FTP", optional ],
+ ]
+ ],
+ [ "Technical restrictions", optional,
+ [
+ [ "BROKEN_EXCEPT_ON_PLATFORM", many ],
+ [ "BROKEN_ON_PLATFORM", many ],
+ [ "NOT_FOR_PLATFORM", many ],
+ [ "ONLY_FOR_PLATFORM", many ],
+ [ "NOT_FOR_COMPILER", many ],
+ [ "ONLY_FOR_COMPILER", many ],
+ [ "NOT_FOR_UNPRIVILEGED", optional ],
+ [ "ONLY_FOR_UNPRIVILEGED", optional ],
+ ]
+ ],
+ [ "Dependencies", optional,
+ [
+ [ "BUILD_DEPENDS", many ],
+ [ "TOOL_DEPENDS", many ],
+ [ "DEPENDS", many ],
+ ]
+ ]
+ );
+
+ if (!defined($seen_Makefile_common) || $seen_Makefile_common) {
+ return;
+ }
+
+ my ($lineno, $sectindex, $varindex) = (0, -1, 0);
+ my ($next_section, $vars, $below, $below_what) = (true, undef, {}, undef);
+
+ # If the current section is optional but contains non-optional
+ # fields, the complete section may be skipped as long as there
+ # has not been a non-optional variable.
+ my $may_skip_section = false;
+
+ # In each iteration, one of the following becomes true:
+ # - new.lineno > old.lineno
+ # - new.sectindex > old.sectindex
+ # - new.sectindex == old.sectindex && new.varindex > old.varindex
+ # - new.next_section == true && old.next_section == false
+ while ($lineno <= $#{$lines}) {
+ my $line = $lines->[$lineno];
+ my $text = $line->text;
+
+ $opt_debug_misc and $line->log_debug("[varorder] section ${sectindex} variable ${varindex}.");
+
+ if ($next_section) {
+ $next_section = false;
+ $sectindex++;
+ last if ($sectindex > $#sections);
+ $vars = $sections[$sectindex]->[2];
+ $may_skip_section = ($sections[$sectindex]->[1] == optional);
+ $varindex = 0;
+ }
+
+ if ($text =~ m"^#") {
+ $lineno++;
+
+ } elsif ($line->has("varcanon")) {
+ my $varcanon = $line->get("varcanon");
+
+ if (exists($below->{$varcanon})) {
+ if (defined($below->{$varcanon})) {
+ $line->log_warning("${varcanon} appears too late. Please put it below $below->{$varcanon}.");
+ } else {
+ $line->log_warning("${varcanon} appears too late. It should be the very first definition.");
+ }
+ $lineno++;
+ next;
+ }
+
+ while ($varindex <= $#{$vars} && $varcanon ne $vars->[$varindex]->[0] && ($vars->[$varindex]->[1] != once || $may_skip_section)) {
+ if ($vars->[$varindex]->[1] == once) {
+ $may_skip_section = false;
+ }
+ $below->{$vars->[$varindex]->[0]} = $below_what;
+ $varindex++;
+ }
+ if ($varindex > $#{$vars}) {
+ if ($sections[$sectindex]->[1] != optional) {
+ $line->log_warning("Empty line expected.");
+ }
+ $next_section = true;
+
+ } elsif ($varcanon ne $vars->[$varindex]->[0]) {
+ $line->log_warning("Expected " . $vars->[$varindex]->[0] . ", but found " . $varcanon . ".");
+ $lineno++;
+
+ } else {
+ if ($vars->[$varindex]->[1] != many) {
+ $below->{$vars->[$varindex]->[0]} = $below_what;
+ $varindex++;
+ }
+ $lineno++;
+ }
+ $below_what = $varcanon;
+
+ } else {
+ while ($varindex <= $#{$vars}) {
+ if ($vars->[$varindex]->[1] == once && !$may_skip_section) {
+ $line->log_warning($vars->[$varindex]->[0] . " should be set here.");
+ }
+ $below->{$vars->[$varindex]->[0]} = $below_what;
+ $varindex++;
+ }
+ $next_section = true;
+ if ($text eq "") {
+ $below_what = "the previous empty line";
+ $lineno++;
+ }
+ }
+ }
+}
+
+sub checklines_mk($) {
+ my ($lines) = @_;
+ my ($allowed_targets) = ({});
+ my ($substcontext) = PkgLint::SubstContext->new();
+
+ assert(@{$lines} != 0, "checklines_mk may only be called with non-empty lines.");
+ $opt_debug_trace and log_debug($lines->[0]->fname, NO_LINES, "checklines_mk()");
+
+ # Define global variables for the Makefile context.
+ $mkctx_indentations = [0];
+ $mkctx_target = undef;
+ $mkctx_for_variables = {};
+ $mkctx_vardef = {};
+ $mkctx_build_defs = {};
+ $mkctx_plist_vars = {};
+ $mkctx_tools = {%{get_predefined_tool_names()}};
+ $mkctx_varuse = {};
+
+ determine_used_variables($lines);
+
+ foreach my $prefix (qw(pre do post)) {
+ foreach my $action (qw(fetch extract patch tools wrapper configure build test install package clean)) {
+ $allowed_targets->{"${prefix}-${action}"} = true;
+ }
+ }
+
+ #
+ # In the first pass, all additions to BUILD_DEFS and USE_TOOLS
+ # are collected to make the order of the definitions irrelevant.
+ #
+
+ foreach my $line (@{$lines}) {
+ next unless $line->has("is_varassign");
+ my $varcanon = $line->get("varcanon");
+
+ if ($varcanon eq "BUILD_DEFS" || $varcanon eq "PKG_GROUPS_VARS" || $varcanon eq "PKG_USERS_VARS") {
+ foreach my $varname (split(qr"\s+", $line->get("value"))) {
+ $mkctx_build_defs->{$varname} = true;
+ $opt_debug_misc and $line->log_debug("${varname} is added to BUILD_DEFS.");
+ }
+
+ } elsif ($varcanon eq "PLIST_VARS") {
+ foreach my $id (split(qr"\s+", $line->get("value"))) {
+ $mkctx_plist_vars->{"PLIST.$id"} = true;
+ $opt_debug_misc and $line->log_debug("PLIST.${id} is added to PLIST_VARS.");
+ use_var($line, "PLIST.$id");
+ }
+
+ } elsif ($varcanon eq "USE_TOOLS") {
+ foreach my $tool (split(qr"\s+", $line->get("value"))) {
+ $tool =~ s/:(build|run)//;
+ $mkctx_tools->{$tool} = true;
+ $opt_debug_misc and $line->log_debug("${tool} is added to USE_TOOLS.");
+ }
+
+ } elsif ($varcanon eq "SUBST_VARS.*") {
+ foreach my $svar (split(/\s+/, $line->get("value"))) {
+ use_var($svar, varname_canon($svar));
+ $opt_debug_misc and $line->log_debug("varuse $svar");
+ }
+
+ } elsif ($varcanon eq "OPSYSVARS") {
+ foreach my $osvar (split(/\s+/, $line->get("value"))) {
+ use_var($line, "$osvar.*");
+ def_var($line, $osvar);
+ }
+ }
+ }
+
+ #
+ # In the second pass, all "normal" checks are done.
+ #
+
+ if (0 <= $#{$lines}) {
+ checkline_rcsid_regex($lines->[0], qr"^#\s+", "# ");
+ }
+
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ checkline_trailing_whitespace($line);
+
+ if ($line->has("is_empty")) {
+ $substcontext->check_end($line);
+
+ } elsif ($line->has("is_comment")) {
+ # No further checks.
+
+ } elsif ($text =~ regex_varassign) {
+ my ($varname, $op, undef, $comment) = ($1, $2, $3, $4);
+ my $space1 = substr($text, $+[1], $-[2] - $+[1]);
+ my $align = substr($text, $+[2], $-[3] - $+[2]);
+ my $value = $line->get("value");
+
+ if ($align !~ m"^(\t*|[ ])$") {
+ $opt_warn_space && $line->log_note("Alignment of variable values should be done with tabs, not spaces.");
+ my $prefix = "${varname}${space1}${op}";
+ my $aligned_len = tablen("${prefix}${align}");
+ if ($aligned_len % 8 == 0) {
+ my $tabalign = ("\t" x (($aligned_len - tablen($prefix) + 7) / 8));
+ $line->replace("${prefix}${align}", "${prefix}${tabalign}");
+ }
+ }
+ checkline_mk_varassign($line, $varname, $op, $value, $comment);
+ $substcontext->check_varassign($line, $varname, $op, $value);
+
+ } elsif ($text =~ regex_mk_shellcmd) {
+ my ($shellcmd) = ($1);
+ checkline_mk_shellcmd($line, $shellcmd);
+
+ } elsif ($text =~ regex_mk_include) {
+ my ($include, $includefile) = ($1, $2);
+
+ $opt_debug_include and $line->log_debug("includefile=${includefile}");
+ checkline_relative_path($line, $includefile, $include eq "include");
+
+ if ($includefile =~ m"../Makefile$") {
+ $line->log_error("Other Makefiles must not be included directly.");
+ $line->explain_warning(
+"If you want to include portions of another Makefile, extract",
+"the common parts and put them into a Makefile.common. After",
+"that, both this one and the other package should include the",
+"Makefile.common.");
+ }
+
+ if ($includefile eq "../../mk/bsd.prefs.mk") {
+ if ($line->fname =~ m"buildlink3\.mk$") {
+ $line->log_note("For efficiency reasons, please include bsd.fast.prefs.mk instead of bsd.prefs.mk.");
+ }
+ $seen_bsd_prefs_mk = true;
+ } elsif ($includefile eq "../../mk/bsd.fast.prefs.mk") {
+ $seen_bsd_prefs_mk = true;
+ }
+
+ if ($includefile =~ m"/x11-links/buildlink3\.mk$") {
+ $line->log_error("${includefile} must not be included directly. Include \"../../mk/x11.buildlink3.mk\" instead.");
+ }
+ if ($includefile =~ m"/jpeg/buildlink3\.mk$") {
+ $line->log_error("${includefile} must not be included directly. Include \"../../mk/jpeg.buildlink3.mk\" instead.");
+ }
+ if ($includefile =~ m"/intltool/buildlink3\.mk$") {
+ $line->log_warning("Please say \"USE_TOOLS+= intltool\" instead of this line.");
+ }
+ if ($includefile =~ m"(.*)/builtin\.mk$") {
+ my ($dir) = ($1);
+ $line->log_error("${includefile} must not be included directly. Include \"${dir}/buildlink3.mk\" instead.");
+ }
+
+ } elsif ($text =~ regex_mk_sysinclude) {
+ my ($includefile, $comment) = ($1, $2);
+
+ # No further action.
+
+ } elsif ($text =~ regex_mk_cond) {
+ my ($indent, $directive, $args, $comment) = ($1, $2, $3, $4);
+
+ use constant regex_directives_with_args => qr"^(?:if|ifdef|ifndef|elif|for|undef)$";
+
+ if ($directive =~ m"^(?:endif|endfor|elif|else)$") {
+ if ($#{$mkctx_indentations} >= 1) {
+ pop(@{$mkctx_indentations});
+ } else {
+ $line->log_error("Unmatched .${directive}.");
+ }
+ }
+
+ # Check the indentation
+ if ($indent ne " " x $mkctx_indentations->[-1]) {
+ $opt_warn_space and $line->log_note("This directive should be indented by ".$mkctx_indentations->[-1]." spaces.");
+ }
+
+ if ($directive eq "if" && $args =~ m"^!defined\([\w]+_MK\)$") {
+ push(@{$mkctx_indentations}, $mkctx_indentations->[-1]);
+
+ } elsif ($directive =~ m"^(?:if|ifdef|ifndef|for|elif|else)$") {
+ push(@{$mkctx_indentations}, $mkctx_indentations->[-1] + 2);
+ }
+
+ if ($directive =~ regex_directives_with_args && !defined($args)) {
+ $line->log_error("\".${directive}\" must be given some arguments.");
+
+ } elsif ($directive !~ regex_directives_with_args && defined($args)) {
+ $line->log_error("\".${directive}\" does not take arguments.");
+
+ if ($directive eq "else") {
+ $line->log_note("If you meant \"else if\", use \".elif\".");
+ }
+
+ } elsif ($directive eq "if" || $directive eq "elif") {
+ checkline_mk_cond($line, $args);
+
+ } elsif ($directive eq "ifdef" || $directive eq "ifndef") {
+ if ($args =~ m"\s") {
+ $line->log_error("The \".${directive}\" directive can only handle _one_ argument.");
+ } else {
+ $line->log_warning("The \".${directive}\" directive is deprecated. Please use \".if "
+ . (($directive eq "ifdef" ? "" : "!"))
+ . "defined(${args})\" instead.");
+ }
+
+ } elsif ($directive eq "for") {
+ if ($args =~ m"^(\S+(?:\s*\S+)*?)\s+in\s+(.*)$") {
+ my ($vars, $values) = ($1, $2);
+
+ foreach my $var (split(qr"\s+", $vars)) {
+ if (!$is_internal && $var =~ m"^_") {
+ $line->log_warning("Variable names starting with an underscore are reserved for internal pkgsrc use.");
+ }
+
+ if ($var =~ m"^[_a-z][_a-z0-9]*$") {
+ # Fine.
+ } elsif ($var =~ m"[A-Z]") {
+ $line->log_warning(".for variable names should not contain uppercase letters.");
+ } else {
+ $line->log_error("Invalid variable name \"${var}\".");
+ }
+
+ $mkctx_for_variables->{$var} = true;
+ }
+
+ # Check if any of the value's types is not guessed.
+ my $guessed = true;
+ foreach my $value (split(qr"\s+", $values)) { # XXX: too simple
+ if ($value =~ m"^\$\{(.*)\}") {
+ my $type = get_variable_type($line, $1);
+ if (defined($type) && !$type->is_guessed()) {
+ $guessed = false;
+ }
+ }
+ }
+
+ my $for_loop_type = PkgLint::Type->new(
+ LK_INTERNAL,
+ "Unchecked",
+ [[qr".*", "pu"]],
+ $guessed
+ );
+ my $for_loop_context = PkgLint::VarUseContext->new(
+ VUC_TIME_LOAD,
+ $for_loop_type,
+ VUC_SHELLWORD_FOR,
+ VUC_EXTENT_WORD
+ );
+ foreach my $var (@{extract_used_variables($line, $values)}) {
+ checkline_mk_varuse($line, $var, "", $for_loop_context);
+ }
+
+ }
+
+ } elsif ($directive eq "undef" && defined($args)) {
+ foreach my $var (split(qr"\s+", $args)) {
+ if (exists($mkctx_for_variables->{$var})) {
+ $line->log_note("Using \".undef\" after a \".for\" loop is unnecessary.");
+ }
+ }
+ }
+
+ } elsif ($text =~ regex_mk_dependency) {
+ my ($targets, $whitespace, $dependencies, $comment) = ($1, $2, $3, $4);
+
+ $opt_debug_misc and $line->log_debug("targets=${targets}, dependencies=${dependencies}");
+ $mkctx_target = $targets;
+
+ foreach my $source (split(/\s+/, $dependencies)) {
+ if ($source eq ".PHONY") {
+ foreach my $target (split(/\s+/, $targets)) {
+ $allowed_targets->{$target} = true;
+ }
+ }
+ }
+
+ foreach my $target (split(/\s+/, $targets)) {
+ if ($target eq ".PHONY") {
+ foreach my $dep (split(qr"\s+", $dependencies)) {
+ $allowed_targets->{$dep} = true;
+ }
+
+ } elsif ($target eq ".ORDER") {
+ # TODO: Check for spelling mistakes.
+
+ } elsif (!exists($allowed_targets->{$target})) {
+ $line->log_warning("Unusual target \"${target}\".");
+ $line->explain_warning(
+"If you really want to define your own targets, you can \"declare\"",
+"them by inserting a \".PHONY: my-target\" line before this line. This",
+"will tell make(1) to not interpret this target's name as a filename.");
+ }
+ }
+
+ } elsif ($text =~ m"^\.\s*(\S*)") {
+ my ($directive) = ($1);
+
+ $line->log_error("Unknown directive \".${directive}\".");
+
+ } elsif ($text =~ m"^ ") {
+ $line->log_warning("Makefile lines should not start with space characters.");
+ $line->explain_warning(
+"If you want this line to contain a shell program, use a tab",
+"character for indentation. Otherwise please remove the leading",
+"white-space.");
+
+ } else {
+ $line->log_error("[Internal] Unknown line format: $text");
+ }
+ }
+ if (@{$lines} > 0) {
+ $substcontext->check_end($lines->[-1]);
+ }
+
+ checklines_trailing_empty_lines($lines);
+
+ if ($#{$mkctx_indentations} != 0) {
+ $lines->[-1]->log_error("Directive indentation is not 0, but ".$mkctx_indentations->[-1]." at EOF.");
+ }
+
+ # Clean up global variables.
+ $mkctx_for_variables = undef;
+ $mkctx_indentations = undef;
+ $mkctx_target = undef;
+ $mkctx_vardef = undef;
+ $mkctx_build_defs = undef;
+ $mkctx_plist_vars = undef;
+ $mkctx_tools = undef;
+ $mkctx_varuse = undef;
+}
+
+sub checklines_buildlink3_inclusion($) {
+ my ($lines) = @_;
+ my ($included_files);
+
+ assert(@{$lines} != 0, "The lines array must be non-empty.");
+ $opt_debug_trace and log_debug($lines->[0]->fname, NO_LINES, "checklines_buildlink3_inclusion()");
+
+ if (!defined($pkgctx_bl3)) {
+ return;
+ }
+
+ # Collect all the included buildlink3.mk files from the file.
+ $included_files = {};
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ regex_mk_include) {
+ my (undef, $file, $comment) = ($1, $2, $3);
+
+ if ($file =~ m"^\.\./\.\./(.*)/buildlink3\.mk") {
+ my ($bl3) = ($1);
+
+ $included_files->{$bl3} = $line;
+ if (!exists($pkgctx_bl3->{$bl3})) {
+ $line->log_warning("${bl3}/buildlink3.mk is included by this file but not by the package.");
+ }
+ }
+ }
+ }
+
+ # Print debugging messages for all buildlink3.mk files that are
+ # included by the package but not by this buildlink3.mk file.
+ foreach my $package_bl3 (sort(keys(%{$pkgctx_bl3}))) {
+ if (!exists($included_files->{$package_bl3})) {
+ $opt_debug_misc and $pkgctx_bl3->{$package_bl3}->log_debug("${package_bl3}/buildlink3.mk is included by the package but not by the buildlink3.mk file.");
+ }
+ }
+}
+
+#
+# Procedures to check a single file.
+#
+
+sub checkfile_ALTERNATIVES($) {
+ my ($fname) = @_;
+ my ($lines);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_ALTERNATIVES()");
+
+ checkperms($fname);
+ if (!($lines = load_file($fname))) {
+ log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return;
+ }
+}
+
+sub checkfile_buildlink3_mk($) {
+ my ($fname) = @_;
+ my ($lines, $lineno, $m);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_buildlink3_mk()");
+
+ checkperms($fname);
+ if (!($lines = load_lines($fname, true))) {
+ log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return;
+ }
+ if (@{$lines} == 0) {
+ log_error($fname, NO_LINES, "Must not be empty.");
+ return;
+ }
+
+ parselines_mk($lines);
+ checklines_mk($lines);
+
+ $lineno = 0;
+
+ # Header comments
+ while ($lineno <= $#{$lines} && (my $text = $lines->[$lineno]->text) =~ m"^#") {
+ if ($text =~ m"^# XXX") {
+ $lines->[$lineno]->log_note("Please read this comment and remove it if appropriate.");
+ }
+ $lineno++;
+ }
+ expect_empty_line($lines, \$lineno);
+
+ if (expect($lines, \$lineno, qr"^BUILDLINK_DEPMETHOD\.(\S+)\?=.*$")) {
+ $lines->[$lineno - 1]->log_warning("This line belongs inside the .ifdef block.");
+ while ($lines->[$lineno]->text eq "") {
+ $lineno++;
+ }
+ }
+
+ if (!($m = expect($lines, \$lineno, qr"^BUILDLINK_TREE\+=\s*(\S+)$"))) {
+ $lines->[$lineno]->log_warning("Expected a BUILDLINK_TREE line.");
+ return;
+ }
+
+ checklines_buildlink3_mk($lines, $lineno, $m->text(1));
+}
+
+sub checklines_buildlink3_mk($$$) {
+ my ($lines, $lineno, $pkgid) = @_;
+ my ($m);
+ my ($bl_PKGBASE_line, $bl_PKGBASE);
+ my ($bl_pkgbase_line, $bl_pkgbase);
+ my ($abi_line, $abi_pkg, $abi_version);
+ my ($api_line, $api_pkg, $api_version);
+
+ # First paragraph: Introduction of the package identifier
+ $bl_pkgbase_line = $lines->[$lineno - 1];
+ $bl_pkgbase = $pkgid;
+ $opt_debug_misc and $bl_pkgbase_line->log_debug("bl_pkgbase=${bl_pkgbase}");
+ expect_empty_line($lines, \$lineno);
+
+ # Second paragraph: multiple inclusion protection and introduction
+ # of the uppercase package identifier.
+ return unless ($m = expect_re($lines, \$lineno, qr"^\.if !defined\((\S+)_BUILDLINK3_MK\)$"));
+ $bl_PKGBASE_line = $lines->[$lineno - 1];
+ $bl_PKGBASE = $m->text(1);
+ $opt_debug_misc and $bl_PKGBASE_line->log_debug("bl_PKGBASE=${bl_PKGBASE}");
+ expect_re($lines, \$lineno, qr"^\Q$bl_PKGBASE\E_BUILDLINK3_MK:=$");
+ expect_empty_line($lines, \$lineno);
+
+ my $norm_bl_pkgbase = $bl_pkgbase;
+ $norm_bl_pkgbase =~ s/-/_/g;
+ $norm_bl_pkgbase = uc($norm_bl_pkgbase);
+ if ($norm_bl_pkgbase ne $bl_PKGBASE) {
+ $bl_PKGBASE_line->log_error("Package name mismatch between ${bl_PKGBASE} ...");
+ $bl_pkgbase_line->log_error("... and ${bl_pkgbase}.");
+ }
+ if (defined($effective_pkgbase) && $effective_pkgbase ne $bl_pkgbase) {
+ $bl_pkgbase_line->log_error("Package name mismatch between ${bl_pkgbase} ...");
+ $effective_pkgname_line->log_error("... and ${effective_pkgbase}.");
+ }
+
+ # Third paragraph: Package information.
+ my $if_level = 1; # the first .if is from the second paragraph.
+ while (true) {
+
+ if ($lineno > $#{$lines}) {
+ lines_log_warning($lines, $lineno, "Expected .endif");
+ return;
+ }
+
+ my $line = $lines->[$lineno];
+
+ if (($m = expect($lines, \$lineno, regex_varassign))) {
+ my ($varname, $value) = ($m->text(1), $m->text(3));
+ my $do_check = false;
+
+ if ($varname eq "BUILDLINK_ABI_DEPENDS.${bl_pkgbase}") {
+ $abi_line = $line;
+ if ($value =~ regex_dependency_lge) {
+ ($abi_pkg, $abi_version) = ($1, $2);
+ } elsif ($value =~ regex_dependency_wildcard) {
+ ($abi_pkg) = ($1);
+ } else {
+ $opt_debug_unchecked and $line->log_debug("Unchecked dependency pattern \"${value}\".");
+ }
+ $do_check = true;
+ }
+ if ($varname eq "BUILDLINK_API_DEPENDS.${bl_pkgbase}") {
+ $api_line = $line;
+ if ($value =~ regex_dependency_lge) {
+ ($api_pkg, $api_version) = ($1, $2);
+ } elsif ($value =~ regex_dependency_wildcard) {
+ ($api_pkg) = ($1);
+ } else {
+ $opt_debug_unchecked and $line->log_debug("Unchecked dependency pattern \"${value}\".");
+ }
+ $do_check = true;
+ }
+ if ($do_check && defined($abi_pkg) && defined($api_pkg)) {
+ if ($abi_pkg ne $api_pkg) {
+ $abi_line->log_warning("Package name mismatch between ${abi_pkg} ...");
+ $api_line->log_warning("... and ${api_pkg}.");
+ }
+ }
+ if ($do_check && defined($abi_version) && defined($api_version)) {
+ if (!dewey_cmp($abi_version, ">=", $api_version)) {
+ $abi_line->log_warning("ABI version (${abi_version}) should be at least ...");
+ $api_line->log_warning("... API version (${api_version}).");
+ }
+ }
+
+ if ($varname =~ m"^BUILDLINK_[\w_]+\.(.*)$") {
+ my ($varparam) = ($1);
+
+ if ($varparam ne $bl_pkgbase) {
+ $line->log_warning("Only buildlink variables for ${bl_pkgbase}, not ${varparam} may be set in this file.");
+ }
+ }
+
+ if ($varname eq "pkgbase") {
+ expect_re($lines, \$lineno, qr"^\.\s*include \"../../mk/pkg-build-options.mk\"$");
+ }
+
+ # TODO: More checks.
+
+ } elsif (expect($lines, \$lineno, qr"^(?:#.*)?$")) {
+ # Comments and empty lines are fine here.
+
+ } elsif (expect($lines, \$lineno, qr"^\.\s*include \"\.\./\.\./([^/]+/[^/]+)/buildlink3\.mk\"$")
+ || expect($lines, \$lineno, qr"^\.\s*include \"\.\./\.\./mk/(\S+)\.buildlink3\.mk\"$")) {
+ # TODO: Maybe check dependency lines.
+
+ } elsif (expect($lines, \$lineno, qr"^\.if\s")) {
+ $if_level++;
+
+ } elsif (expect($lines, \$lineno, qr"^\.endif.*$")) {
+ $if_level--;
+ last if $if_level == 0;
+
+ } else {
+ $opt_debug_unchecked and lines_log_warning($lines, $lineno, "Unchecked line in third paragraph.");
+ $lineno++;
+ }
+ }
+ if (!defined($api_line)) {
+ $lines->[$lineno - 1]->log_warning("Definition of BUILDLINK_API_DEPENDS is missing.");
+ }
+ expect_empty_line($lines, \$lineno);
+
+ # Fourth paragraph: Cleanup, corresponding to the first paragraph.
+ return unless expect_re($lines, \$lineno, qr"^BUILDLINK_TREE\+=\s*-\Q$bl_pkgbase\E$");
+
+ if ($lineno <= $#{$lines}) {
+ $lines->[$lineno]->log_warning("The file should end here.");
+ }
+
+ checklines_buildlink3_inclusion($lines);
+}
+
+sub checkfile_DESCR($) {
+ my ($fname) = @_;
+ my ($maxchars, $maxlines) = (80, 24);
+ my ($lines);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_DESCR()");
+
+ 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;
+ }
+
+ foreach my $line (@{$lines}) {
+ checkline_length($line, $maxchars);
+ checkline_trailing_whitespace($line);
+ checkline_valid_characters($line, regex_validchars);
+ if ($line->text =~ m"\$\{") {
+ $line->log_warning("Variables are not expanded in the DESCR file.");
+ }
+ }
+ checklines_trailing_empty_lines($lines);
+
+ if (@{$lines} > $maxlines) {
+ my $line = $lines->[$maxlines];
+
+ $line->log_warning("File too long (should be no more than $maxlines lines).");
+ $line->explain_warning(
+"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.");
+ }
+ autofix($lines);
+}
+
+sub checkfile_distinfo($) {
+ my ($fname) = @_;
+ my ($lines, %in_distinfo, $patches_dir, $di_is_committed, $current_fname, $is_patch, @seen_algs);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_distinfo()");
+
+ $di_is_committed = is_committed($fname);
+
+ 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_note("Empty line expected.");
+ $lines->[1]->explain_note("This is merely for aesthetical purposes.");
+ }
+
+ $patches_dir = $patchdir;
+ if (!defined($patches_dir) && -d "${current_dir}/patches") {
+ $patches_dir = "patches";
+ }
+
+ my $on_filename_change = sub($$) {
+ my ($line, $new_fname) = @_;
+
+ if (defined($current_fname)) {
+ my $seen_algs = join(", ", @seen_algs);
+ $opt_debug_misc and $line->log_debug("File ${current_fname} has checksums ${seen_algs}.");
+ if ($is_patch) {
+ if ($seen_algs ne "SHA1") {
+ $line->log_error("Expected SHA1 checksum for ${current_fname}, got ${seen_algs}.");
+ }
+ } else {
+ if ($seen_algs ne "SHA1, RMD160, Size" && $seen_algs ne "SHA1, RMD160, SHA512, Size") {
+ $line->log_error("Expected SHA1, RMD160, Size checksums for ${current_fname}, got ${seen_algs}.");
+ }
+ }
+ }
+
+ $is_patch = defined($new_fname) && $new_fname =~ m"^patch-.+$" && -f "${current_dir}/${patches_dir}/${new_fname}";
+ $current_fname = $new_fname;
+ @seen_algs = ();
+ };
+
+ foreach my $line (@{$lines}[2..$#{$lines}]) {
+ if ($line->text !~ m"^(\w+) \(([^)]+)\) = (.*)(?: bytes)?$") {
+ $line->log_error("Unknown line type.");
+ next;
+ }
+ my ($alg, $chksum_fname, $sum) = ($1, $2, $3);
+
+ if (!defined($current_fname) || $chksum_fname ne $current_fname) {
+ $on_filename_change->($line, $chksum_fname);
+ }
+
+ if ($chksum_fname !~ m"^\w") {
+ $line->log_error("All file names should start with a letter.");
+ }
+
+ # Inter-package check for differing distfile checksums.
+ if ($opt_check_global && !$is_patch) {
+ # Note: Perl-specific auto-population.
+ if (exists($ipc_distinfo->{$alg}->{$chksum_fname})) {
+ my $other = $ipc_distinfo->{$alg}->{$chksum_fname};
+
+ if ($other->[1] eq $sum) {
+ # Fine.
+ } else {
+ $line->log_error("The ${alg} checksum for ${chksum_fname} differs ...");
+ $other->[0]->log_error("... from this one.");
+ }
+ } else {
+ $ipc_distinfo->{$alg}->{$chksum_fname} = [$line, $sum];
+ }
+ }
+
+ push(@seen_algs, $alg);
+
+ if ($is_patch && defined($patches_dir) && !(defined($distinfo_file) && $distinfo_file eq "./../../lang/php5/distinfo")) {
+ my $fname = "${current_dir}/${patches_dir}/${chksum_fname}";
+ if ($di_is_committed && !is_committed($fname)) {
+ $line->log_warning("${patches_dir}/${chksum_fname} is registered in distinfo but not added to CVS.");
+ }
+
+ if (open(my $patchfile, "<", $fname)) {
+ my $sha1 = Digest::SHA1->new();
+ while (defined(my $patchline = <$patchfile>)) {
+ $sha1->add($patchline) unless $patchline =~ m"\$[N]etBSD";
+ }
+ close($patchfile);
+ my $chksum = $sha1->hexdigest();
+ if ($sum ne $chksum) {
+ $line->log_error("${alg} checksum of ${chksum_fname} differs (expected ${sum}, got ${chksum}). Rerun '".conf_make." makepatchsum'.");
+ }
+ } else {
+ $line->log_warning("${chksum_fname} does not exist.");
+ $line->explain_warning(
+"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?");
+ }
+ }
+ $in_distinfo{$chksum_fname} = true;
+
+ }
+ $on_filename_change->(PkgLint::Line->new($fname, NO_LINE_NUMBER, "", []), undef);
+ checklines_trailing_empty_lines($lines);
+
+ if (defined($patches_dir)) {
+ foreach my $patch (glob("${current_dir}/${patches_dir}/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_extra($) {
+ my ($fname) = @_;
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_extra()");
+
+ my $lines = load_file($fname) or return log_error($fname, NO_LINE_NUMBER, "Could not be read.");
+ checklines_trailing_empty_lines($lines);
+ checkperms($fname);
+}
+
+sub checkfile_INSTALL($) {
+ my ($fname) = @_;
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_INSTALL()");
+
+ checkperms($fname);
+ my $lines = load_file($fname) or return log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+}
+
+sub checkfile_MESSAGE($) {
+ my ($fname) = @_;
+
+ 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.");
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_MESSAGE()");
+
+ checkperms($fname);
+ my $lines = load_file($fname) or return log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+
+ if (@{$lines} < 3) {
+ log_warning($fname, NO_LINE_NUMBER, "File too short.");
+ explain_warning($fname, NO_LINE_NUMBER, @explanation);
+ return;
+ }
+ if ($lines->[0]->text ne "=" x 75) {
+ $lines->[0]->log_warning("Expected a line of exactly 75 \"=\" characters.");
+ explain_warning($fname, NO_LINE_NUMBER, @explanation);
+ }
+ checkline_rcsid($lines->[1], "");
+ foreach my $line (@{$lines}) {
+ checkline_length($line, 80);
+ checkline_trailing_whitespace($line);
+ checkline_valid_characters($line, regex_validchars);
+ }
+ if ($lines->[-1]->text ne "=" x 75) {
+ $lines->[-1]->log_warning("Expected a line of exactly 75 \"=\" characters.");
+ explain_warning($fname, NO_LINE_NUMBER, @explanation);
+ }
+ checklines_trailing_empty_lines($lines);
+}
+
+sub checkfile_mk($) {
+ my ($fname) = @_;
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_mk()");
+
+ checkperms($fname);
+ my $lines = load_lines($fname, true) or return log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+
+ parselines_mk($lines);
+ checklines_mk($lines);
+ autofix($lines);
+}
+
+sub checkfile_package_Makefile($$) {
+ my ($fname, $lines) = @_;
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_package_Makefile(..., ...)");
+
+ checkperms($fname);
+
+ if (!exists($pkgctx_vardef->{"PLIST_SRC"})
+ && !exists($pkgctx_vardef->{"GENERATE_PLIST"})
+ && !exists($pkgctx_vardef->{"META_PACKAGE"})
+ && defined($pkgdir)
+ && !-f "${current_dir}/$pkgdir/PLIST"
+ && !-f "${current_dir}/$pkgdir/PLIST.common") {
+ log_warning($fname, NO_LINE_NUMBER, "Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset. Are you sure PLIST handling is ok?");
+ }
+
+ if ((exists($pkgctx_vardef->{"NO_CHECKSUM"}) || $pkgctx_vardef->{"META_PACKAGE"}) && is_emptydir("${current_dir}/${patchdir}")) {
+ if (-f "${current_dir}/${distinfo_file}") {
+ log_warning("${current_dir}/${distinfo_file}", NO_LINE_NUMBER, "This file should not exist if NO_CHECKSUM or META_PACKAGE is set.");
+ }
+ } else {
+ if (!-f "${current_dir}/${distinfo_file}") {
+ log_warning("${current_dir}/${distinfo_file}", NO_LINE_NUMBER, "File not found. Please run '".conf_make." makesum'.");
+ }
+ }
+
+ if (exists($pkgctx_vardef->{"REPLACE_PERL"}) && exists($pkgctx_vardef->{"NO_CONFIGURE"})) {
+ $pkgctx_vardef->{"REPLACE_PERL"}->log_warning("REPLACE_PERL is ignored when ...");
+ $pkgctx_vardef->{"NO_CONFIGURE"}->log_warning("... NO_CONFIGURE is set.");
+ }
+
+ if (!exists($pkgctx_vardef->{"LICENSE"})) {
+ log_error($fname, NO_LINE_NUMBER, "Each package must define its LICENSE.");
+ }
+
+ if (exists($pkgctx_vardef->{"GNU_CONFIGURE"}) && exists($pkgctx_vardef->{"USE_LANGUAGES"})) {
+ my $languages_line = $pkgctx_vardef->{"USE_LANGUAGES"};
+ my $value = $languages_line->get("value");
+
+ if ($languages_line->has("comment") && $languages_line->get("comment") =~ m"\b(?:c|empty|none)\b"i) {
+ # Don't emit a warning, since the comment
+ # probably contains a statement that C is
+ # really not needed.
+
+ } elsif ($value !~ m"(?:^|\s+)(?:c|c99|objc)(?:\s+|$)") {
+ $pkgctx_vardef->{"GNU_CONFIGURE"}->log_warning("GNU_CONFIGURE almost always needs a C compiler, ...");
+ $languages_line->log_warning("... but \"c\" is not added to USE_LANGUAGES.");
+ }
+ }
+
+ my $distname_line = $pkgctx_vardef->{"DISTNAME"};
+ my $pkgname_line = $pkgctx_vardef->{"PKGNAME"};
+
+ my $distname = defined($distname_line) ? $distname_line->get("value") : undef;
+ my $pkgname = defined($pkgname_line) ? $pkgname_line->get("value") : undef;
+ my $nbpart = get_nbpart();
+
+ # Let's do some tricks to get the proper value of the package
+ # name more often.
+ if (defined($distname) && defined($pkgname)) {
+ $pkgname =~ s/\$\{DISTNAME\}/$distname/;
+
+ if ($pkgname =~ m"^(.*)\$\{DISTNAME:S(.)([^:]*)\2([^:]*)\2(g?)\}(.*)$") {
+ my ($before, $separator, $old, $new, $mod, $after) = ($1, $2, $3, $4, $5, $6);
+ my $newname = $distname;
+ $old = quotemeta($old);
+ $old =~ s/^\\\^/^/;
+ $old =~ s/\\\$$/\$/;
+ if ($mod eq "g") {
+ $newname =~ s/$old/$new/g;
+ } else {
+ $newname =~ s/$old/$new/;
+ }
+ $opt_debug_misc and $pkgname_line->log_debug("old pkgname=$pkgname");
+ $pkgname = $before . $newname . $after;
+ $opt_debug_misc and $pkgname_line->log_debug("new pkgname=$pkgname");
+ }
+ }
+
+ if (defined($pkgname) && defined($distname) && $pkgname eq $distname) {
+ $pkgname_line->log_note("PKGNAME is \${DISTNAME} by default. You probably don't need to define PKGNAME.");
+ }
+
+ if (!defined($pkgname) && defined($distname) && $distname !~ regex_unresolved && $distname !~ regex_pkgname) {
+ $distname_line->log_warning("As DISTNAME is not a valid package name, please define the PKGNAME explicitly.");
+ }
+
+ ($effective_pkgname, $effective_pkgname_line, $effective_pkgbase, $effective_pkgversion)
+ = (defined($pkgname) && $pkgname !~ regex_unresolved && $pkgname =~ regex_pkgname) ? ($pkgname.$nbpart, $pkgname_line, $1, $2)
+ : (defined($distname) && $distname !~ regex_unresolved && $distname =~ regex_pkgname) ? ($distname.$nbpart, $distname_line, $1, $2)
+ : (undef, undef, undef, undef);
+ if (defined($effective_pkgname_line)) {
+ $opt_debug_misc and $effective_pkgname_line->log_debug("Effective name=${effective_pkgname} base=${effective_pkgbase} version=${effective_pkgversion}.");
+ # XXX: too many false positives
+ if (false && $pkgpath =~ m"/([^/]+)$" && $effective_pkgbase ne $1) {
+ $effective_pkgname_line->log_warning("Mismatch between PKGNAME ($effective_pkgname) and package directory ($1).");
+ }
+ }
+
+ checkpackage_possible_downgrade();
+
+ if (!exists($pkgctx_vardef->{"COMMENT"})) {
+ log_warning($fname, NO_LINE_NUMBER, "No COMMENT given.");
+ }
+
+ if (exists($pkgctx_vardef->{"USE_IMAKE"}) && exists($pkgctx_vardef->{"USE_X11"})) {
+ $pkgctx_vardef->{"USE_IMAKE"}->log_note("USE_IMAKE makes ...");
+ $pkgctx_vardef->{"USE_X11"}->log_note("... USE_X11 superfluous.");
+ }
+
+ if (defined($effective_pkgbase)) {
+
+ foreach my $suggested_update (@{get_suggested_package_updates()}) {
+ my ($line, $suggbase, $suggver, $suggcomm) = @{$suggested_update};
+ my $comment = (defined($suggcomm) ? " (${suggcomm})" : "");
+
+ next unless $effective_pkgbase eq $suggbase;
+
+ if (dewey_cmp($effective_pkgversion, "<", $suggver)) {
+ $effective_pkgname_line->log_warning("This package should be updated to ${suggver}${comment}.");
+ $effective_pkgname_line->explain_warning(
+"The wishlist for package updates in doc/TODO mentions that a newer",
+"version of this package is available.");
+ }
+ if (dewey_cmp($effective_pkgversion, "==", $suggver)) {
+ $effective_pkgname_line->log_note("The update request to ${suggver} from doc/TODO${comment} has been done.");
+ }
+ if (dewey_cmp($effective_pkgversion, ">", $suggver)) {
+ $effective_pkgname_line->log_note("This package is newer than the update request to ${suggver}${comment}.");
+ }
+ }
+ }
+
+ checklines_mk($lines);
+ checklines_package_Makefile_varorder($lines);
+ autofix($lines);
+}
+
+#include PkgLint/Patches.pm
+
+sub checkfile_PLIST($) {
+ my ($fname) = @_;
+ my ($lines, $last_file_seen);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile_PLIST()");
+
+ 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], "\@comment ");
+
+ if (@$lines == 1) {
+ $lines->[0]->log_warning("PLIST files shouldn't be empty.");
+ $lines->[0]->explain_warning(
+"One reason for empty PLISTs is that this is a newly created package",
+"and that the author didn't run \"bmake print-PLIST\" after installing",
+"the files.",
+"",
+"Another reason, common for Perl packages, is that the final PLIST is",
+"automatically generated. Since the source PLIST is not used at all,",
+"you can remove it.",
+"",
+"Meta packages also don't need a PLIST file.");
+ }
+
+ # Get the list of all files from the PLIST.
+ my $all_files = {};
+ my $all_dirs = {};
+ my $extra_lines = [];
+ if (basename($fname) eq "PLIST.common_end") {
+ my $common_lines = load_file(dirname($fname) . "/PLIST.common");
+ if ($common_lines) {
+ $extra_lines = $common_lines;
+ }
+ }
+
+ foreach my $line (@{$extra_lines}, @{$lines}) {
+ my $text = $line->text;
+
+ if (index($text, '${') != -1 && $text =~ m"\$\{([\w_]+)\}(.*)") {
+ if (defined($pkgctx_plist_subst_cond) && exists($pkgctx_plist_subst_cond->{$1})) {
+ $opt_debug_misc and $line->log_debug("Removed PLIST_SUBST conditional $1.");
+ $text = $2;
+ }
+ }
+
+ if ($text =~ m"^[\w\$]") {
+ $all_files->{$text} = $line;
+ my $dir = $text;
+ while ($dir =~ s,/[^/]+$,,) {
+ $all_dirs->{$dir} = $line;
+ }
+ }
+ if (substr($text, 0, 1) eq '@' && $text =~ m"^\@exec \$\{MKDIR\} %D/(.*)$") {
+ my $dir = $1;
+ do {
+ $all_dirs->{$dir} = $line;
+ } while ($dir =~ s,/[^/]+$,,);
+ }
+ }
+
+ foreach my $line (@{$lines}) {
+ my $text = $line->text;
+
+ if ($text =~ /\s$/) {
+ $line->log_error("pkgsrc does not support filenames ending in white-space.");
+ $line->explain_error(
+"Each character in the PLIST is relevant, even trailing white-space.");
+ }
+
+ # @foo directives.
+ if (index($text, '@') != -1 && $text =~ /^(?:\$\{[\w_]+\})?\@([a-z-]+)\s+(.*)/) {
+ my ($cmd, $arg) = ($1, $2);
+
+ if ($cmd eq "unexec" && $arg =~ m"^(rmdir|\$\{RMDIR\} \%D/)(.*)") {
+ my ($rmdir, $rest) = ($1, $2);
+ if ($rest !~ m"(?:true|\$\{TRUE\})") {
+ $line->log_warning("Please remove this line. It is no longer necessary.");
+ }
+
+ } 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 !~ m"/usr/bin/true") {
+ $line->log_error("ldconfig must be used with \"||/usr/bin/true\".");
+ }
+
+ } elsif ($cmd eq "comment") {
+ # nothing to do
+
+ } elsif ($cmd eq "dirrm") {
+ $line->log_warning("\@dirrm is obsolete. Please remove this line.");
+ $line->explain_warning(
+"Directories are removed automatically when they are empty.",
+"When a package needs an empty directory, it can use the \@pkgdir",
+"command in the PLIST");
+ } elsif ($cmd eq "imake-man") {
+ my (@args) = split(/\s+/, $arg);
+ if (@args != 3) {
+ $line->log_warning("Invalid number of arguments for imake-man.");
+ } else {
+ if ($args[2] eq "\${IMAKE_MANNEWSUFFIX}") {
+ warn_about_PLIST_imake_mannewsuffix($line);
+ }
+ }
+
+ } elsif ($cmd eq "pkgdir") {
+ # TODO: What can we check here?
+
+ } else {
+ $line->log_warning("Unknown PLIST directive \"\@$cmd\".");
+ }
+
+ # Pathnames.
+ } elsif ($text =~ m"^([A-Za-z0-9\$].*)/([^/]+)$") {
+ my ($dirname, $basename) = ($1, $2);
+
+ if ($opt_warn_plist_sort && $text =~ m"^\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}.");
+ $line->explain_warning(
+"For aesthetic reasons, the files in the PLIST should be sorted",
+"alphabetically.");
+ } elsif ($last_file_seen eq $text) {
+ $line->log_warning("Duplicate filename.");
+ }
+ }
+ $last_file_seen = $text;
+ }
+
+ if (index($basename, '${IMAKE_MANNEWSUFFIX}') != -1) {
+ warn_about_PLIST_imake_mannewsuffix($line);
+ }
+
+ if (substr($dirname, 0, 4) eq "bin/") {
+ $line->log_warning("The bin/ directory should not have subdirectories.");
+
+ } elsif ($dirname eq "bin") {
+
+ if (exists($all_files->{"man/man1/${basename}.1"})) {
+ # Fine.
+ } elsif (exists($all_files->{"man/man6/${basename}.6"})) {
+ # Fine.
+ } elsif (exists($all_files->{"\${IMAKE_MAN_DIR}/${basename}.\${IMAKE_MANNEWSUFFIX}"})) {
+ # Fine.
+ } else {
+ $opt_warn_extra and $line->log_warning("Manual page missing for bin/${basename}.");
+ $opt_warn_extra and $line->explain_warning(
+"All programs that can be run directly by the user should have a manual",
+"page for quick reference. The programs in the bin/ directory should have",
+"corresponding manual pages in section 1 (filename program.1), not in",
+"section 8.");
+ }
+
+ } elsif (substr($text, 0, 4) eq "doc/") {
+ $line->log_error("Documentation must be installed under share/doc, not doc.");
+
+ } elsif (substr($text, 0, 9) eq "etc/rc.d/") {
+ $line->log_error("RCD_SCRIPTS must not be registered in the PLIST. Please use the RCD_SCRIPTS framework.");
+
+ } elsif (substr($text, 0, 4) eq "etc/") {
+ my $f = "mk/pkginstall/bsd.pkginstall.mk";
+
+ assert(-f "${cwd_pkgsrcdir}/${f}", "${cwd_pkgsrcdir}/${f} is not a regular file.");
+ $line->log_error("Configuration files must not be registered in the PLIST. Please use the CONF_FILES framework, which is described in ${f}.");
+
+ } elsif (substr($text, 0, 8) eq "include/" && $text =~ m"^include/.*\.(?:h|hpp)$") {
+ # Fine.
+
+ } elsif ($text eq "info/dir") {
+ $line->log_error("\"info/dir\" must not be listed. Use install-info to add/remove an entry.");
+
+ } elsif (substr($text, 0, 5) eq "info/" && length($text) > 5) {
+ if (defined($pkgctx_vardef) && !exists($pkgctx_vardef->{"INFO_FILES"})) {
+ $line->log_warning("Packages that install info files should set INFO_FILES.");
+ }
+
+ } elsif (defined($effective_pkgbase) && $text =~ m"^lib/\Q${effective_pkgbase}\E/") {
+ # Fine.
+
+ } elsif (substr($text, 0, 11) eq "lib/locale/") {
+ $line->log_error("\"lib/locale\" must not be listed. Use \${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead.");
+
+ } elsif (substr($text, 0, 4) eq "lib/" && $text =~ m"^(lib/(?:.*/)*)([^/]+)\.(so|a|la)$") {
+ my ($dir, $lib, $ext) = ($1, $2, $3);
+
+ if ($dir eq "lib/" && $lib !~ m"^lib") {
+ $opt_warn_extra and $line->log_warning("Library filename does not start with \"lib\".");
+ }
+ if ($ext eq "la") {
+ if (defined($pkgctx_vardef) && !exists($pkgctx_vardef->{"USE_LIBTOOL"})) {
+ $line->log_warning("Packages that install libtool libraries should define USE_LIBTOOL.");
+ }
+ }
+
+ } elsif (substr($text, 0, 4) eq "man/" && $text =~ m"^man/(cat|man)(\w+)/(.*?)\.(\w+)(\.gz)?$") {
+ my ($cat_or_man, $section, $manpage, $ext, $gz) = ($1, $2, $3, $4, $5);
+
+ if ($section !~ m"^[\dln]$") {
+ $line->log_warning("Unknown section \"${section}\" for manual page.");
+ }
+
+ if ($cat_or_man eq "cat" && !exists($all_files->{"man/man${section}/${manpage}.${section}"})) {
+ $line->log_warning("Preformatted manual page without unformatted one.");
+ }
+
+ if ($cat_or_man eq "cat") {
+ if ($ext ne "0") {
+ $line->log_warning("Preformatted manual pages should end in \".0\".");
+ }
+ } else {
+ if ($section ne $ext) {
+ $line->log_warning("Mismatch between the section (${section}) and extension (${ext}) of the manual page.");
+ }
+ }
+
+ if (defined($gz)) {
+ $line->log_note("The .gz extension is unnecessary for manual pages.");
+ $line->explain_note(
+"Whether the manual pages are installed in compressed form or not is",
+"configured by the pkgsrc user. Compression and decompression takes place",
+"automatically, no matter if the .gz extension is mentioned in the PLIST",
+"or not.");
+ }
+
+ } elsif (substr($text, 0, 7) eq "man/cat") {
+ $line->log_warning("Invalid filename \"${text}\" for preformatted manual page.");
+
+ } elsif (substr($text, 0, 7) eq "man/man") {
+ $line->log_warning("Invalid filename \"${text}\" for unformatted manual page.");
+
+ } elsif (substr($text, 0, 5) eq "sbin/") {
+ my $binname = substr($text, 5);
+
+ if (!exists($all_files->{"man/man8/${binname}.8"})) {
+ $opt_warn_extra and $line->log_warning("Manual page missing for sbin/${binname}.");
+ $opt_warn_extra and $line->explain_warning(
+"All programs that can be run directly by the user should have a manual",
+"page for quick reference. The programs in the sbin/ directory should have",
+"corresponding manual pages in section 8 (filename program.8), not in",
+"section 1.");
+ }
+
+ } elsif (substr($text, 0, 6) eq "share/" && $text =~ m"^share/applications/.*\.desktop$") {
+ my $f = "../../sysutils/desktop-file-utils/desktopdb.mk";
+ if (defined($pkgctx_included) && !exists($pkgctx_included->{$f})) {
+ $line->log_warning("Packages that install a .desktop entry may .include \"$f\".");
+ $line->explain_warning(
+"If *.desktop files contain MimeType keys, global MIME Type registory DB",
+"must be updated by desktop-file-utils.",
+"Otherwise, this warning is harmless.");
+ }
+
+ } elsif (substr($text, 0, 6) eq "share/" && $pkgpath ne "graphics/hicolor-icon-theme" && $text =~ m"^share/icons/hicolor(?:$|/)") {
+ my $f = "../../graphics/hicolor-icon-theme/buildlink3.mk";
+ if (defined($pkgctx_included) && !exists($pkgctx_included->{$f})) {
+ $line->log_error("Please .include \"$f\" in the Makefile");
+ $line->explain_error(
+"If hicolor icon themes are installed, icon theme cache must be",
+"maintained. The hicolor-icon-theme package takes care of that.");
+ }
+
+ } elsif (substr($text, 0, 6) eq "share/" && $pkgpath ne "graphics/gnome-icon-theme" && $text =~ m"^share/icons/gnome(?:$|/)") {
+ my $f = "../../graphics/gnome-icon-theme/buildlink3.mk";
+ if (defined($pkgctx_included) && !exists($pkgctx_included->{$f})) {
+ $line->log_error("Please .include \"$f\"");
+ $line->explain_error(
+"If Gnome icon themes are installed, icon theme cache must be maintained.");
+ }
+ } elsif ($dirname eq "share/aclocal" && $basename =~ m"\.m4$") {
+ # Fine.
+
+ } elsif (substr($text, 0, 15) eq "share/doc/html/") {
+ $opt_warn_plist_depr and $line->log_warning("Use of \"share/doc/html\" is deprecated. Use \"share/doc/\${PKGBASE}\" instead.");
+
+ } elsif (defined($effective_pkgbase) && $text =~ m"^share/(?:doc/|examples/|)\Q${effective_pkgbase}\E/") {
+ # Fine.
+
+ } elsif ($pkgpath ne "graphics/hicolor-icon-theme" && $text =~ m"^share/icons/hicolor/icon-theme\.cache") {
+ $line->log_error("Please .include \"../../graphics/hicolor-icon-theme/buildlink3.mk\" and remove this line.");
+
+ } elsif (substr($text, 0, 11) eq "share/info/") {
+ $line->log_warning("Info pages should be installed into info/, not share/info/.");
+ $line->explain_warning(
+"To fix this, you should add INFO_FILES=yes to the package Makefile.");
+
+ } elsif (substr($text, -3) eq ".mo" && $text =~ m"^share/locale/[\w\@_]+/LC_MESSAGES/[^/]+\.mo$") {
+ # Fine.
+
+ } elsif (substr($text, 0, 10) eq "share/man/") {
+ $line->log_warning("Man pages should be installed into man/, not share/man/.");
+
+ } else {
+ $opt_debug_unchecked and $line->log_debug("Unchecked pathname \"${text}\".");
+ }
+
+ if ($text =~ /\$\{PKGLOCALEDIR}/ && defined($pkgctx_vardef) && !exists($pkgctx_vardef->{"USE_PKGLOCALEDIR"})) {
+ $line->log_warning("PLIST contains \${PKGLOCALEDIR}, but USE_PKGLOCALEDIR was not found.");
+ }
+
+ if (index($text, "/CVS/") != -1) {
+ $line->log_warning("CVS files should not be in the PLIST.");
+ }
+ if (substr($text, -5) eq ".orig") {
+ $line->log_warning(".orig files should not be in the PLIST.");
+ }
+ if (substr($text, -14) eq "/perllocal.pod") {
+ $line->log_warning("perllocal.pod files should not be in the PLIST.");
+ $line->explain_warning(
+"This file is handled automatically by the INSTALL/DEINSTALL scripts,",
+"since its contents changes frequently.");
+ }
+
+ if ($text =~ m"^(.*)(\.a|\.so[0-9.]*)$") {
+ my ($basename, $ext) = ($1, $2);
+
+ if (exists($all_files->{"${basename}.la"})) {
+ $line->log_warning("Redundant library found. The libtool library is in line " . $all_files->{"${basename}.la"}->lines . ".");
+ }
+ }
+
+ } elsif ($text =~ m"^\$\{[\w_]+\}$") {
+ # A variable on its own line.
+
+ } else {
+ $line->log_error("Unknown line type.");
+ }
+ }
+ checklines_trailing_empty_lines($lines);
+ autofix($lines);
+}
+
+sub checkfile($) {
+ my ($fname) = @_;
+ my ($st, $basename);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkfile()");
+
+ $basename = basename($fname);
+ if ($basename =~ m"^(?:work.*|.*~|.*\.orig|.*\.rej)$") {
+ if ($opt_import) {
+ log_error($fname, NO_LINE_NUMBER, "Must be cleaned up before committing the package.");
+ }
+ return;
+ }
+
+ if (!($st = lstat($fname))) {
+ log_error($fname, NO_LINE_NUMBER, "$!");
+ return;
+ }
+ if (S_ISDIR($st->mode)) {
+ if ($basename eq "files" || $basename eq "patches" || $basename eq "CVS") {
+ # Ok
+ } elsif ($fname =~ m"(?:^|/)files/[^/]*$") {
+ # Ok
+
+ } elsif (!is_emptydir($fname)) {
+ log_warning($fname, NO_LINE_NUMBER, "Unknown directory name.");
+ }
+
+ } elsif (S_ISLNK($st->mode)) {
+ if ($basename !~ m"^work") {
+ log_warning($fname, NO_LINE_NUMBER, "Unknown symlink name.");
+ }
+
+ } elsif (!S_ISREG($st->mode)) {
+ log_error($fname, NO_LINE_NUMBER, "Only files and directories are allowed in pkgsrc.");
+
+ } elsif ($basename eq "ALTERNATIVES") {
+ $opt_check_ALTERNATIVES and checkfile_ALTERNATIVES($fname);
+
+ } elsif ($basename eq "buildlink3.mk") {
+ $opt_check_bl3 and checkfile_buildlink3_mk($fname);
+
+ } elsif ($basename =~ m"^DESCR") {
+ $opt_check_DESCR and checkfile_DESCR($fname);
+
+ } elsif ($basename =~ m"^distinfo") {
+ $opt_check_distinfo and checkfile_distinfo($fname);
+
+ } elsif ($basename eq "DEINSTALL" || $basename eq "INSTALL") {
+ $opt_check_INSTALL and checkfile_INSTALL($fname);
+
+ } elsif ($basename =~ m"^MESSAGE") {
+ $opt_check_MESSAGE and checkfile_MESSAGE($fname);
+
+ } elsif ($basename =~ m"^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$") {
+ $opt_check_patches and checkfile_patch($fname);
+
+ } elsif ($fname =~ m"(?:^|/)patches/manual[^/]*$") {
+ $opt_debug_unchecked and log_debug($fname, NO_LINE_NUMBER, "Unchecked file \"${fname}\".");
+
+ } elsif ($fname =~ m"(?:^|/)patches/[^/]*$") {
+ log_warning($fname, NO_LINE_NUMBER, "Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.");
+
+ } elsif ($basename =~ m"^(?:.*\.mk|Makefile.*)$" and not $fname =~ m,files/, and not $fname =~ m,patches/,) {
+ $opt_check_mk and checkfile_mk($fname);
+
+ } elsif ($basename =~ m"^PLIST") {
+ $opt_check_PLIST and checkfile_PLIST($fname);
+
+ } elsif ($basename eq "TODO" || $basename eq "README") {
+ # Ok
+
+ } elsif ($basename =~ m"^CHANGES-.*") {
+ load_doc_CHANGES($fname);
+
+ } elsif (!-T $fname) {
+ log_warning($fname, NO_LINE_NUMBER, "Unexpectedly found a binary file.");
+
+ } elsif ($fname =~ m"(?:^|/)files/[^/]*$") {
+ # Ok
+ } else {
+ log_warning($fname, NO_LINE_NUMBER, "Unexpected file found.");
+ $opt_check_extra and checkfile_extra($fname);
+ }
+}
+
+sub my_split($$) {
+ my ($delimiter, $s) = @_;
+ my ($pos, $next, @result);
+
+ $pos = 0;
+ for ($pos = 0; $pos != -1; $pos = $next) {
+ $next = index($s, $delimiter, $pos);
+ push @result, (($next == -1) ? substr($s, $pos) : substr($s, $pos, $next - $pos));
+ if ($next != -1) {
+ $next += length($delimiter);
+ }
+ }
+ return @result;
+}
+
+# Checks that the files in the directory are in sync with CVS's status.
+#
+sub checkdir_CVS($) {
+ my ($fname) = @_;
+
+ my $cvs_entries = load_file("$fname/CVS/Entries");
+ my $cvs_entries_log = load_file("$fname/CVS/Entries.Log");
+ return unless $cvs_entries;
+
+ foreach my $line (@$cvs_entries) {
+ my ($type, $fname, $mtime, $date, $keyword_mode, $tag, $undef) = my_split("/", $line->text);
+ next if ($type eq "D" && !defined($fname));
+ assert(false, "Unknown line format: " . $line->text)
+ unless $type eq "" || $type eq "D";
+ assert(false, "Unknown line format: " . $line->text)
+ unless defined($tag);
+ assert(false, "Unknown line format: " . $line->text)
+ unless defined($keyword_mode);
+ assert(false, "Unknown line format: " . $line->text)
+ if defined($undef);
+ }
+}
+
+#
+# Procedures to check a directory including the files in it.
+#
+
+sub checkdir_root() {
+ my ($fname) = "${current_dir}/Makefile";
+ my ($lines, $prev_subdir, @subdirs);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkdir_root()");
+
+ if (!($lines = load_lines($fname, true))) {
+ log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return;
+ }
+
+ parselines_mk($lines);
+ if (0 <= $#{$lines}) {
+ checkline_rcsid_regex($lines->[0], qr"#\s+", "# ");
+ }
+
+ foreach my $line (@{$lines}) {
+ if ($line->text =~ m"^(#?)SUBDIR\s*\+=(\s*)(\S+)\s*(?:#\s*(.*?)\s*|)$") {
+ my ($comment_flag, $indentation, $subdir, $comment) = ($1, $2, $3, $4);
+
+ if ($comment_flag eq "#" && (!defined($comment) || $comment eq "")) {
+ $line->log_warning("${subdir} commented out without giving a reason.");
+ }
+
+ if ($indentation ne "\t") {
+ $line->log_warning("Indentation should be a single tab character.");
+ }
+
+ if ($subdir =~ m"\$" || !-f "${current_dir}/${subdir}/Makefile") {
+ next;
+ }
+
+ if (!defined($prev_subdir) || $subdir gt $prev_subdir) {
+ # correctly ordered
+ } elsif ($subdir eq $prev_subdir) {
+ $line->log_error("${subdir} must only appear once.");
+ } elsif ($prev_subdir eq "x11" && $subdir eq "archivers") {
+ # ignore that one, since it is documented in the top-level Makefile
+ } else {
+ $line->log_warning("${subdir} should come before ${prev_subdir}.");
+ }
+
+ $prev_subdir = $subdir;
+
+ if ($comment_flag eq "") {
+ push(@subdirs, "${current_dir}/${subdir}");
+ }
+ }
+ }
+
+ checklines_mk($lines);
+
+ if ($opt_recursive) {
+ $ipc_checking_root_recursively = true;
+ push(@todo_items, @subdirs);
+ }
+}
+
+sub checkdir_category() {
+ my $fname = "${current_dir}/Makefile";
+ my ($lines, $lineno);
+
+ $opt_debug_trace and log_debug($fname, NO_LINES, "checkdir_category()");
+
+ if (!($lines = load_lines($fname, true))) {
+ log_error($fname, NO_LINE_NUMBER, "Cannot be read.");
+ return;
+ }
+ parselines_mk($lines);
+
+ $lineno = 0;
+
+ # The first line must contain the RCS Id
+ if ($lineno <= $#{$lines} && checkline_rcsid_regex($lines->[$lineno], qr"#\s+", "# ")) {
+ $lineno++;
+ }
+
+ # Then, arbitrary comments may follow
+ while ($lineno <= $#{$lines} && $lines->[$lineno]->text =~ m"^#") {
+ $lineno++;
+ }
+
+ # Then we need an empty line
+ expect_empty_line($lines, \$lineno);
+
+ # Then comes the COMMENT line
+ if ($lineno <= $#{$lines} && $lines->[$lineno]->text =~ m"^COMMENT=\t*(.*)") {
+ my ($comment) = ($1);
+
+ checkline_valid_characters_in_variable($lines->[$lineno], qr"[-\040'(),/0-9A-Za-z]");
+ $lineno++;
+ } else {
+ $lines->[$lineno]->log_error("COMMENT= line expected.");
+ }
+
+ # Then we need an empty line
+ expect_empty_line($lines, \$lineno);
+
+ # And now to the most complicated part of the category Makefiles,
+ # the (hopefully) sorted list of SUBDIRs. The first step is to
+ # collect the SUBDIRs in the Makefile and in the file system.
+
+ my (@f_subdirs, @m_subdirs);
+
+ @f_subdirs = sort(get_subdirs($current_dir));
+
+ my $prev_subdir = undef;
+ while ($lineno <= $#{$lines}) {
+ my $line = $lines->[$lineno];
+
+ if ($line->text =~ m"^(#?)SUBDIR\+=(\s*)(\S+)\s*(?:#\s*(.*?)\s*|)$") {
+ my ($comment_flag, $indentation, $subdir, $comment) = ($1, $2, $3, $4);
+
+ if ($comment_flag eq "#" && (!defined($comment) || $comment eq "")) {
+ $line->log_warning("${subdir} commented out without giving a reason.");
+ }
+
+ if ($indentation ne "\t") {
+ $line->log_warning("Indentation should be a single tab character.");
+ }
+
+ if (defined($prev_subdir) && $subdir eq $prev_subdir) {
+ $line->log_error("${subdir} must only appear once.");
+ } elsif (defined($prev_subdir) && $subdir lt $prev_subdir) {
+ $line->log_warning("${subdir} should come before ${prev_subdir}.");
+ } else {
+ # correctly ordered
+ }
+
+ push(@m_subdirs, [$subdir, $line, $comment_flag ? false : true]);
+ $prev_subdir = $subdir;
+ $lineno++;
+
+ } else {
+ if ($line->text ne "") {
+ $line->log_error("SUBDIR+= line or empty line expected.");
+ }
+ last;
+ }
+ }
+
+ # To prevent unnecessary warnings about subdirectories that are
+ # in one list, but not in the other, we generate the sets of
+ # subdirs of each list.
+ my (%f_check, %m_check);
+ foreach my $f (@f_subdirs) { $f_check{$f} = true; }
+ foreach my $m (@m_subdirs) { $m_check{$m->[0]} = true; }
+
+ my ($f_index, $f_atend, $f_neednext, $f_current) = (0, false, true, undef, undef);
+ my ($m_index, $m_atend, $m_neednext, $m_current) = (0, false, true, undef, undef);
+ my ($line, $m_recurse);
+ my (@subdirs);
+
+ while (!($m_atend && $f_atend)) {
+
+ if (!$m_atend && $m_neednext) {
+ $m_neednext = false;
+ if ($m_index > $#m_subdirs) {
+ $m_atend = true;
+ $line = $lines->[$lineno];
+ next;
+ } else {
+ $m_current = $m_subdirs[$m_index]->[0];
+ $line = $m_subdirs[$m_index]->[1];
+ $m_recurse = $m_subdirs[$m_index]->[2];
+ $m_index++;
+ }
+ }
+
+ if (!$f_atend && $f_neednext) {
+ $f_neednext = false;
+ if ($f_index > $#f_subdirs) {
+ $f_atend = true;
+ next;
+ } else {
+ $f_current = $f_subdirs[$f_index++];
+ }
+ }
+
+ if (!$f_atend && ($m_atend || $f_current lt $m_current)) {
+ if (!exists($m_check{$f_current})) {
+ $line->log_error("${f_current} exists in the file system, but not in the Makefile.");
+ $line->append_before("SUBDIR+=\t${f_current}");
+ }
+ $f_neednext = true;
+
+ } elsif (!$m_atend && ($f_atend || $m_current lt $f_current)) {
+ if (!exists($f_check{$m_current})) {
+ $line->log_error("${m_current} exists in the Makefile, but not in the file system.");
+ $line->delete();
+ }
+ $m_neednext = true;
+
+ } else { # $f_current eq $m_current
+ $f_neednext = true;
+ $m_neednext = true;
+ if ($m_recurse) {
+ push(@subdirs, "${current_dir}/${m_current}");
+ }
+ }
+ }
+
+ # the wip category Makefile may have its own targets for generating
+ # indexes and READMEs. Just skip them.
+ if ($is_wip) {
+ while ($lineno <= $#{$lines} - 2) {
+ $lineno++;
+ }
+ }
+
+ expect_empty_line($lines, \$lineno);
+
+ # And, last but not least, the .include line
+ my $final_line = ".include \"../mk/bsd.pkg.subdir.mk\"";
+ expect($lines, \$lineno, qr"\Q$final_line\E")
+ || expect_text($lines, \$lineno, ".include \"../mk/misc/category.mk\"");
+
+ if ($lineno <= $#{$lines}) {
+ $lines->[$lineno]->log_error("The file should end here.");
+ }
+
+ checklines_mk($lines);
+
+ autofix($lines);
+
+ if ($opt_recursive) {
+ unshift(@todo_items, @subdirs);
+ }
+}
+
+sub checkdir_package() {
+ my ($lines, $have_distinfo, $have_patches);
+
+ # Initialize global variables
+ $pkgdir = undef;
+ $filesdir = "files";
+ $patchdir = "patches";
+ $distinfo_file = "distinfo";
+ $effective_pkgname = undef;
+ $effective_pkgbase = undef;
+ $effective_pkgversion = undef;
+ $effective_pkgname_line = undef;
+ $seen_bsd_prefs_mk = false;
+ $pkgctx_vardef = {%{get_userdefined_variables()}};
+ $pkgctx_varuse = {};
+ $pkgctx_bl3 = {};
+ $pkgctx_plist_subst_cond = {};
+ $pkgctx_included = {};
+ $seen_Makefile_common = false;
+
+ # we need to handle the Makefile first to get some variables
+ if (!load_package_Makefile("${current_dir}/Makefile", \$lines)) {
+ log_error("${current_dir}/Makefile", NO_LINE_NUMBER, "Cannot be read.");
+ goto cleanup;
+ }
+
+ my @files = glob("${current_dir}/*");
+ if ($pkgdir ne ".") {
+ push(@files, glob("${current_dir}/${pkgdir}/*"));
+ }
+ if ($opt_check_extra) {
+ push(@files, glob("${current_dir}/${filesdir}/*"));
+ }
+ push(@files, glob("${current_dir}/${patchdir}/*"));
+ if ($distinfo_file !~ m"^(?:\./)?distinfo$") {
+ push(@files, "${current_dir}/${distinfo_file}");
+ }
+ $have_distinfo = false;
+ $have_patches = false;
+
+ # Determine the used variables before checking any of the
+ # Makefile fragments.
+ foreach my $fname (@files) {
+ if (($fname =~ m"^((?:.*/)?Makefile\..*|.*\.mk)$")
+ && (not $fname =~ m"patch-")
+ && (not $fname =~ m"${pkgdir}/")
+ && (not $fname =~ m"${filesdir}/")
+ && (defined(my $lines = load_lines($fname, true)))) {
+ parselines_mk($lines);
+ determine_used_variables($lines);
+ }
+ }
+
+ foreach my $fname (@files) {
+ if ($fname eq "${current_dir}/Makefile") {
+ $opt_check_Makefile and checkfile_package_Makefile($fname, $lines);
+ } else {
+ checkfile($fname);
+ }
+ if ($fname =~ m"/patches/patch-*$") {
+ $have_patches = true;
+ } elsif ($fname =~ m"/distinfo$") {
+ $have_distinfo = true;
+ }
+ }
+
+ if ($opt_check_distinfo && $opt_check_patches) {
+ if ($have_patches && ! $have_distinfo) {
+ log_warning("${current_dir}/$distinfo_file", NO_LINE_NUMBER, "File not found. Please run '".conf_make." makepatchsum'.");
+ }
+ }
+
+ if (!is_emptydir("${current_dir}/scripts")) {
+ log_warning("${current_dir}/scripts", NO_LINE_NUMBER, "This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's Makefile.");
+ }
+
+cleanup:
+ # Clean up global variables.
+ $pkgdir = undef;
+ $filesdir = undef;
+ $patchdir = undef;
+ $distinfo_file = undef;
+ $effective_pkgname = undef;
+ $effective_pkgbase = undef;
+ $effective_pkgversion = undef;
+ $effective_pkgname_line = undef;
+ $seen_bsd_prefs_mk = undef;
+ $pkgctx_vardef = undef;
+ $pkgctx_varuse = undef;
+ $pkgctx_bl3 = undef;
+ $pkgctx_plist_subst_cond = undef;
+ $pkgctx_included = undef;
+ $seen_Makefile_common = undef;
+}
+
+#
+# Selecting the proper checking procedures for a directory entry.
+#
+
+sub checkitem($) {
+ my ($item) = @_;
+ my ($st, $is_dir, $is_reg);
+
+ if (!($st = lstat($item))) {
+ log_error($item, NO_LINE_NUMBER, "Does not exist.");
+ return;
+ }
+
+ $is_dir = S_ISDIR($st->mode);
+ $is_reg = S_ISREG($st->mode);
+ if (!$is_reg && !$is_dir) {
+ log_error($item, NO_LINE_NUMBER, "Must be a file or directory.");
+ return;
+ }
+
+ $current_dir = $is_dir ? $item : dirname($item);
+ my $abs_current_dir = Cwd::abs_path($current_dir);
+ $is_wip = !$opt_import && ($abs_current_dir =~ m"/wip(?:/|$)");
+ $is_internal = ($abs_current_dir =~ m"/mk(?:/|$)");
+
+ # Determine the root directory of pkgsrc. By only overwriting
+ # the global variable $cwd_pkgsrcdir when we are checking inside
+ # a pkgsrc tree, the user can specify a tree with the
+ # --pkgsrcdir option and then check files (but not directories)
+ # outside of any pkgsrc tree.
+ $cur_pkgsrcdir = undef;
+ $pkgpath = undef;
+ foreach my $d (".", "..", "../..", "../../..") {
+ if (-f "${current_dir}/${d}/mk/bsd.pkg.mk") {
+ $cur_pkgsrcdir = $d;
+ $pkgpath = relative_path("${current_dir}/${d}", $current_dir);
+ }
+ }
+ if (!defined($cwd_pkgsrcdir) && defined($cur_pkgsrcdir)) {
+ $cwd_pkgsrcdir = "${current_dir}/${cur_pkgsrcdir}";
+ }
+
+ if (!defined($cwd_pkgsrcdir)) {
+ log_error($item, NO_LINE_NUMBER, "Cannot determine the pkgsrc root directory.");
+ return;
+ }
+
+ check_pkglint_version(); # (needs $cwd_pkgsrcdir)
+
+ return if $is_dir && is_emptydir($item);
+
+ if ($is_dir) {
+ checkdir_CVS($item);
+ }
+
+ if ($is_reg) {
+ checkfile($item);
+
+ } elsif (!defined($cur_pkgsrcdir)) {
+ log_error($item, NO_LINES, "Cannot check directories outside a pkgsrc tree.");
+
+ } elsif ($cur_pkgsrcdir eq "../..") {
+ checkdir_package();
+
+ } elsif ($cur_pkgsrcdir eq "..") {
+ checkdir_category();
+
+ } elsif ($cur_pkgsrcdir eq ".") {
+ checkdir_root();
+
+ } else {
+ log_error($item, NO_LINE_NUMBER, "Don't know how to check this directory.");
+ }
+}
+
+#
+# The main program
+#
+
+sub main() {
+
+ local $| = true;
+ parse_command_line();
+
+ @todo_items = (@ARGV != 0) ? @ARGV : (".");
+ while (@todo_items != 0) {
+ checkitem(shift(@todo_items));
+ }
+
+ if ($ipc_checking_root_recursively) {
+ check_unused_licenses();
+ }
+
+ PkgLint::Logging::print_summary_and_exit($opt_quiet);
+}
+
+main() unless caller();
diff --git a/pkgtools/pkglint4/files/pkglint.t b/pkgtools/pkglint4/files/pkglint.t
new file mode 100644
index 00000000000..13c56e61323
--- /dev/null
+++ b/pkgtools/pkglint4/files/pkglint.t
@@ -0,0 +1,198 @@
+#! @PERL@
+# $NetBSD: pkglint.t,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+
+require 'pkglint.pl'; # so we can test its internals
+$pkglint::program = 'pkglint.pl'; # because it self-greps for vartypes
+
+use Test::More tests => 36;
+use Test::Deep;
+use Test::Trap;
+
+use Config;
+use File::Basename;
+use IO::File;
+use IPC::Open3;
+use Symbol qw(gensym);
+
+use warnings;
+use strict;
+
+sub test_unit {
+ my ($unit, $params, $exitcode, $stdout_re, $stderr_re) = @_;
+ $stdout_re ||= '^$';
+ $stderr_re ||= '^$';
+
+ my @results = trap { $unit->(@$params) };
+
+ if (defined $exitcode) {
+ is($trap->exit, $exitcode, qq{exits $exitcode});
+ } else {
+ is($trap->exit, undef, q{doesn't exit});
+ }
+ like($trap->stdout, qr/$stdout_re/sm, qq{stdout matches $stdout_re});
+ like($trap->stderr, qr/$stderr_re/sm, qq{stderr matches $stderr_re});
+
+ return @results;
+}
+
+sub test_program {
+ my ($command, $params, $exitcode, $stdout_re, $stderr_re) = @_;
+ $stdout_re ||= '^$';
+ $stderr_re ||= '^$';
+
+ my $stdout = '';
+ my $stderr = '';
+ local *CATCHERR = IO::File->new_tmpfile;
+ my $pid = open3(gensym(), \*CATCHOUT, ">&CATCHERR", $command, @$params);
+ while (my $l = <CATCHOUT>) {
+ $stdout .= $l;
+ }
+ waitpid($pid, 0);
+ my $ret = $? >> 8;
+ seek CATCHERR, 0, 0;
+ while (my $l = <CATCHERR>) {
+ $stderr .= $l;
+ }
+
+ if (defined $exitcode) {
+ is($ret, $exitcode, qq{exits $exitcode});
+ }
+ like($stdout, qr/$stdout_re/sm, qq{stdout matches $stdout_re});
+ like($stderr, qr/$stderr_re/sm, qq{stderr matches $stderr_re});
+
+ # return @results;
+}
+
+sub test_get_vartypes_basictypes {
+ my $unit = \&pkglint::get_vartypes_basictypes;
+
+ my @results = test_unit($unit);
+ my %types = %{$results[0]};
+
+ my @all_vartypes_basictypes = qw(
+ AwkCommand BrokenIn
+ BuildlinkDepmethod BuildlinkDepth BuildlinkPackages
+ CFlag Category Comment
+ Dependency DependencyWithPath
+ DistSuffix EmulPlatform
+ FetchURL FileMode Filemask Filename
+ Identifier Integer LdFlag License Mail_Address Message Option
+ Pathlist Pathmask Pathname
+ Perl5Packlist
+ PkgName PkgOptionsVar PkgPath PkgRevision
+ PlatformTriple PrefixPathname PythonDependency
+ RelativePkgDir RelativePkgPath
+ Restricted
+ SedCommand SedCommands
+ ShellCommand ShellWord
+ Stage String Tool URL Unchecked UserGroupName Varname Version
+ WrapperReorder WrapperTransform
+ WrkdirSubdirectory WrksrcSubdirectory
+ Yes YesNo YesNo_Indirectly
+ );
+
+ cmp_bag(
+ [ keys %types ],
+ \@all_vartypes_basictypes,
+ q{types contains all expected and no unexpected types},
+ );
+}
+
+sub test_get_vartypes_map {
+ my $unit = \&pkglint::get_vartypes_map;
+
+ my @results = test_unit($unit);
+ my %map = %{$results[0]};
+ is($map{'BSD_MAKE_ENV'}->basic_type(), 'ShellWord',
+ q{a couple expected vars are typed right});
+ is($map{'USE_BUILTIN.*'}->basic_type(), 'YesNo_Indirectly',
+ q{a couple expected vars are typed right});
+}
+
+sub test_checkline_mk_vartype_basic {
+ # this is what gets self-grepped: all that "elsif ($type eq"
+ # sub doesn't return anything, just warns or errors if need be
+ #
+ # TODO:
+ #
+ # test a shallow one and then a deeply nested one
+ # (type='Restricted', value='incorrect')
+ # (type='Restricted', value='RESTRICTED')
+ # (type='SedCommands', a few different values')
+ # once test coverage is persuasive, refactor to a dispatch table
+ # once refactored, get rid of the $pkglint::program global
+}
+
+sub test_pkglint_main {
+ my $unit = \&pkglint::main;
+
+ @ARGV = ('-h');
+ test_unit($unit, undef, 0, '^usage: pkglint ', '^$');
+
+ @ARGV = ('..');
+ test_unit($unit, undef, 0, '^looks fine', '^$');
+
+ @ARGV = ('.');
+ test_unit($unit, undef, 1, '^ERROR:.+how to check', '^$');
+
+ @ARGV = ();
+ test_unit($unit, undef, 1, '^ERROR:.+how to check', '^$');
+
+ @ARGV = ('/does/not/exist');
+ test_unit($unit, undef, 1, '^ERROR:.+not exist', '^$');
+
+ @ARGV = ($ENV{HOME});
+ test_unit($unit, undef, 1, '^ERROR:.+outside a pkgsrc', '^$');
+}
+
+sub test_lint_some_reference_packages {
+ my %reference_packages = (
+ 'devel/syncdir' => {
+ stdout_re => <<EOT,
+^ERROR: .*Makefile: Each package must define its LICENSE\.
+ERROR: .*patches/patch-aa:[0-9]+: Comment expected\.
+2 errors and 0 warnings found\..*\$
+EOT
+ stderr_re => undef,
+ exitcode => 1,
+ },
+ 'mail/qmail' => {
+ stdout_re => <<EOT,
+^WARN: .*Makefile:[0-9]+: USERGROUP_PHASE is defined but not used\. Spelling mistake\\?
+0 errors and 1 warnings found\..*\$
+EOT
+ stderr_re => undef,
+ exitcode => 0,
+ },
+ 'mail/getmail' => {
+ stdout_re => <<EOT,
+^looks fine\.\$
+EOT
+ stderr_re => undef,
+ exitcode => 0,
+ },
+ );
+
+ my $dirprefix = dirname($0) || '.';
+ my $pkglint = "$dirprefix/pkglint.pl";
+ my $perl = $Config{perlpath};
+ for my $package (keys %reference_packages) {
+ test_program($perl, [ $pkglint, "@PKGSRCDIR@/$package" ],
+ $reference_packages{$package}->{exitcode},
+ $reference_packages{$package}->{stdout_re},
+ $reference_packages{$package}->{stderr_re});
+ }
+ # XXX this is JUST like test_unit(), when the tests work, refactor!
+
+}
+
+sub main {
+ test_get_vartypes_basictypes();
+ test_get_vartypes_map();
+ test_checkline_mk_vartype_basic();
+ test_pkglint_main();
+ test_lint_some_reference_packages();
+}
+
+main();
diff --git a/pkgtools/pkglint4/files/plist-clash.pl b/pkgtools/pkglint4/files/plist-clash.pl
new file mode 100644
index 00000000000..1b3f1c52465
--- /dev/null
+++ b/pkgtools/pkglint4/files/plist-clash.pl
@@ -0,0 +1,60 @@
+#! @PERL@
+# $NetBSD: plist-clash.pl,v 1.1 2015/11/25 16:42:21 rillig Exp $
+#
+# Scan all PLIST files given on the command line and report all lines
+# that appear more than once.
+
+my %files = ();
+
+sub read_PLIST($) {
+ my ($fname) = @_;
+
+ if (!open(F, "<", $fname)) {
+ warn "$!\n";
+ return undef;
+ }
+
+ my $lineno = 0;
+ foreach my $line (<F>) {
+ chomp($line);
+ $lineno++;
+
+ # Ignore comments and commands
+ next if ($line =~ qr"^@");
+
+ # Ignore filenames with embedded variables
+ next if ($line =~ qr"\$");
+
+ if ($line =~ qr"^[A-Za-z0-9].*") {
+ if (!exists($files{$line})) {
+ $files{$line} = [];
+ }
+ push(@{$files{$line}}, "$fname:$lineno");
+
+ } else {
+ warn("ERROR: $fname:$lineno: Unknown line type\n");
+ }
+ }
+ close(F);
+}
+
+sub main() {
+ if (@ARGV == 0) {
+ die("usage: $0 <plist>...\n");
+ }
+
+ foreach my $plist (@ARGV) {
+ read_PLIST($plist);
+ }
+
+ foreach my $file (sort keys %files) {
+ my $srcs = $files{$file};
+ if (@{$srcs} != 1) {
+ foreach my $src (@{$srcs}) {
+ print "$src: $file\n";
+ }
+ }
+ }
+}
+
+main();