[SCM] Debian package checker branch, master, updated. 2.5.1-22-g10bc91d
The following commit has been merged in the master branch:
commit 10bc91d36bab9858fba4b291cdb2f96dad94119f
Author: Niels Thykier <niels@thykier.net>
Date: Mon Jun 27 15:39:59 2011 +0200
Merge branch 'vendor-profile' into master
This (squashed) commit adds Vendor Profile support for
Lintian. lintian and lintian-info now supports the option
--profile <vendor/profile>
In the absence of that (or --ftp-master-rejects for
lintian), they will fallback to using dpkg-vendor
(or Dpkg::Vendor) to find the first related vendor
that has a profile and use that.
The full specification of the Vendor Profiles have been
added to the Lintian User Manual.
diff --git a/debian/changelog b/debian/changelog
index 186e0d4..a29d857 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -42,11 +42,39 @@ lintian (2.5.2) UNRELEASED; urgency=low
* data/rules/known-makefiles:
+ [NT] Added file.
+ * debian/lintian.install:
+ + [NT] Added the profiles directory.
+ * debian/rules:
+ + [NT] Added target to update the profiles.
+
+ * doc/lintian.xml:
+ + [NT] Added information about the new Vendor profiles.
+
+ * frontend/{lintian,lintian-info}:
+ + [NT] Added profile support (new option --profile), please
+ refer to the Lintian User Manual for more information.
* frontend/lintian:
+ [NT] Ensure that all dependency collections of a check are
loaded. Previously Lintian would only load direct
dependencies of a collection and assume that was enough.
+ * lib/Lintian/{Internal/FrontendUtil,Profile}.pm:
+ + [NT] New files.
+ * lib/Lintian/{Tag/Info,Tags}.pm:
+ + [NT] Updated for profile support.
+
+ * man/lintian.pod.in:
+ + [NT] Documented the new --profile option. Note that the
+ options --suppress-tags{,-from-file} now supresses tags
+ in the active profile.
+ * man/lintian-info.pod:
+ + [NT] Documented the new --profile option.
+
+ * profiles/*:
+ + [NT] Added default profiles for Debian and Ubuntu. Note
+ that these profiles will silently ignore overrides for
+ "fatal" (non-overridable) tags. (Closes: #536364)
+
-- Niels Thykier <niels@thykier.net> Sun, 19 Jun 2011 13:39:10 +0200
lintian (2.5.1) unstable; urgency=low
diff --git a/debian/lintian.install b/debian/lintian.install
index 4a17707..b222bc2 100644
--- a/debian/lintian.install
+++ b/debian/lintian.install
@@ -4,4 +4,5 @@ checks usr/share/lintian
collection usr/share/lintian
data usr/share/lintian
lib usr/share/lintian
+profiles usr/share/lintian
unpack usr/share/lintian
diff --git a/debian/rules b/debian/rules
index 75ffe9b..626bf81 100755
--- a/debian/rules
+++ b/debian/rules
@@ -4,7 +4,9 @@ PERL ?= /usr/bin/perl
VER := $(shell head -1 debian/changelog | sed -e 's/^.*(//' -e 's/).*$$//')
tmp := $(CURDIR)/debian/lintian
-neededfiles := debian/rules frontend/lintian
+profiles := profiles/debian/main.profile \
+ profiles/debian/ftp-master-auto-reject.profile
+neededfiles := debian/rules frontend/lintian $(profiles)
docsource := doc/lintian.xml doc/README.in man/lintian.pod.in \
man/lintian-info.pod
allchecks := $(wildcard checks/*)
@@ -26,7 +28,9 @@ ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
PAR_ARGS=-j $(jobs)
endif
-
+profiles: $(profiles)
+$(profiles): $(allchecks) private/generate-profiles.pl
+ LINTIAN_ROOT='.' private/generate-profiles.pl
runtests: $(neededfiles) $(allchecks) $(allcollect) $(tagfiles) $(testfiles)
@echo .... running tests ....
diff --git a/doc/lintian.xml b/doc/lintian.xml
index 7d479d5..94d2965 100644
--- a/doc/lintian.xml
+++ b/doc/lintian.xml
@@ -538,6 +538,200 @@ foo source: configure-generated-file-in-source config.cache
in the additional info.
</para>
</sect1>
+ <sect1 label="2.5" id="section-2.5">
+ <title>Vendor Profiles</title>
+ <para>
+ Vendor profiles allows vendors and users to customize Lintian
+ without having to modify the underlying code. If a profile
+ is not explicitly given, Lintian will derive the best
+ possible profile for the current vendor from dpkg-vendor.
+ </para>
+ <sect2 label="2.5.1" id="section-2.5.1">
+ <title>Rules for profile names and location</title>
+ <para>
+ Profile names should only consist of the lower case
+ characters ([a-z]), underscore (_), dash (-) and
+ forward slashes (/). Particularly note that dot (.) are
+ specificially <emphasis>not</emphasis> allowed in a
+ profile name.
+ </para>
+ <para>
+ The default profile for a vendor is called
+ <filename>$VENDOR/main</filename>. If Lintian sees a
+ profile name without a slash, it is taken as a short
+ form of the default profile for a vendor with that
+ name.
+ </para>
+ <para>
+ The filename for the profile is derived from the name by
+ simply concatenating it with <filename>.profile</filename>,
+ Lintian will then look for a file with that name in the
+ follownig directories:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <filename>$HOME/.lintian/profiles</filename>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <filename>/etc/lintian/profiles</filename>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <filename>$LINTIAN_ROOT/profiles</filename>
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ Note that an implication of the handling of default
+ vendor profiles implies that profiles must be in
+ subdirectories of the directories above for Lintian
+ to recognise them.
+ </para>
+ </sect2>
+ <sect2 label="2.5.2" id="section-2.5.2">
+ <title>Profile syntax and semantics</title>
+ <para>
+ Profiles are written in the same syntax as Debian
+ control files as described in the
+ <ulink url="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-controlsyntax">
+ Debian Policy Manual §5.1</ulink>. Profiles allow
+ comments as described.
+ </para>
+ <sect3 label="2.5.2.1" id="section-2.5.2.1">
+ <title>Main profile paragraph</title>
+ <para>
+ The fields in the first paragraph are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><emphasis>Profile</emphasis> (simple, mandatory)</term>
+ <listitem>
+ <para>
+ Name of the profile.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Extends</emphasis> (simple, optional)</term>
+ <listitem>
+ <para>
+ Name of the (parent) profile, which this profile
+ extends. Lintian will recursively process the
+ extended profile before continuing with processing
+ this profile. In the absence of this field, the
+ profile is not based on another profile.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Enable-Tags-From-Check</emphasis> (folded, optional)</term>
+ <listitem>
+ <para>
+ Comma-separated list of checks. All tags from each
+ check listed will be enabled in this profile.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Disable-Tags-From-Check</emphasis> (folded, optional)</term>
+ <listitem>
+ <para>
+ Comma-separated list of checks. All tags from each
+ check listed will be disabled in this profile.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Enable-Tags</emphasis> (folded, optional)</term>
+ <listitem>
+ <para> Comma-separated list of tags that should be
+ enabled.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Disable-Tags</emphasis> (folded, optional)</term>
+ <listitem>
+ <para>
+ Comma-separated list of tags that should be
+ disabled.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ The profile is invalid and is rejected, if Enable-Tags and
+ Disable-Tags lists the same tag twice - even if it is in
+ the same field. This holds analogously for
+ Enable-Tags-From-Check and Disable-Tags-From-Check.
+ </para>
+ <para>
+ It is allowed to allowed to list a tag in Enable-Tags or
+ Disable-Tags even if the check that provides this tag is
+ listed in the Disable-Tags-From-Check or
+ Enable-Tags-From-Check field. In case of conflict,
+ Enable-Tags / Disable-Tags shall overrule
+ Disable-Tags-From-Check / Enable-Tags-From-Check within
+ the profile.
+ </para>
+ <para>
+ It is not an error to enable or disable a check that is
+ already enabled or disabled respectively (e.g. by a
+ parent profile).
+ </para>
+ <para>
+ A profile is invalid if it directly or indirectly extends
+ itself or if it extends an invalid profile.
+ </para>
+ </sect3>
+ <sect3 label="2.5.2.2" id="section-2.5.2.2">
+ <title>Tag alteration paragraphs</title>
+ <para>
+ The fields in the secondary paragraphs are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><emphasis>Tags</emphasis> (folded, mandatory)</term>
+ <listitem>
+ <para>
+ Comma separated list of tags affected by this
+ paragraph.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Overridable</emphasis> (simple, optional)</term>
+ <listitem>
+ <para>
+ Either "Yes" or "No", which decides whether these
+ tags can be overriden. Overrides for these tags
+ will silently be ignored.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><emphasis>Severity</emphasis> (simple, optional)</term>
+ <listitem>
+ <para>
+ The value must be a valid tag severity; the severity
+ of the affected is set to this value. Note
+ that <emphasis>experimental</emphasis> is not a
+ severity.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ The paragraph must contain at least one other field than the
+ Tag field.
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
<chapter label="3" id="chapter-3">
diff --git a/frontend/lintian b/frontend/lintian
index 982982b..30cdb82 100755
--- a/frontend/lintian
+++ b/frontend/lintian
@@ -27,6 +27,7 @@ use warnings;
use Getopt::Long;
use Cwd;
+
# }}}
# {{{ Global Variables
@@ -51,6 +52,7 @@ our $ftpmaster_tags = 0; #flag for -F|--ftp-master-rejects switch
my $allow_root = 0; #flag for --allow-root switch
my $keep_lab = 0; #flag for --keep-lab switch
my $packages_file = 0; #string for the -p option
+our $OPT_LINTIAN_PROFILE = ''; #string for the --profile option
our $OPT_LINTIAN_LAB = ''; #string for the --lab option
our $OPT_LINTIAN_ARCHIVEDIR = '';#string for the --archivedir option
our $OPT_LINTIAN_DIST = ''; #string for the --dist option
@@ -62,6 +64,15 @@ our $LINTIAN_ROOT; #location of the lintian modules
my $no_conf = 0; #flag for --no-cfg
my %opt; #hash of some flags from cmd or cfg
my %conf_opt; #names of options set in the cfg file
+my $no_profile = 0; #whether a profile should be loaded
+
+# The profile search path except LINTIAN_ROOT/profiles
+# which will be added later (we dont know LINTIAN_ROOT
+# at this point)
+my @prof_inc = (
+ "$ENV{HOME}/.lintian/profiles",
+ '/etc/lintian/profiles'
+);
my $experimental_output_opts = undef;
@@ -96,6 +107,7 @@ our $LINTIAN_DIST = undef;
our $LINTIAN_ARCH = undef;
our $LINTIAN_SECTION = undef;
our $LINTIAN_AREA = undef;
+our $LINTIAN_PROFILE = undef;
# }}}
# {{{ Setup Code
@@ -163,6 +175,7 @@ Behaviour options:
-L, --display-level display tags with the specified level
-o, --no-override ignore overrides
--pedantic display "P:" tags (normally suppressed)
+ --profile X Use the profile X or use vendor X checks
--show-overrides output tags that have been overriden
--suppress-tags T,... don\'t show the specified tags
--suppress-tags-from-file X don\'t show the tags listed in file X
@@ -221,6 +234,7 @@ sub record_check_part {
}
$action = 'check';
$checks = "$_[1]";
+ $no_profile = 1;
}
# Record Parts requested for checking
@@ -240,6 +254,7 @@ sub record_check_tags {
}
$action = 'check';
$check_tags = "$_[1]";
+ $no_profile = 1;
}
# Record Parts requested for checking
@@ -437,6 +452,7 @@ my %opthash = ( # ------------------ actions
'area=s' => \$OPT_LINTIAN_AREA,
'section=s' => \$OPT_LINTIAN_AREA,
'arch=s' => \$OPT_LINTIAN_ARCH,
+ 'profile=s' => \$OPT_LINTIAN_PROFILE,
'root=s' => \$LINTIAN_ROOT,
# ------------------ package selection options
@@ -499,6 +515,10 @@ if ($action =~ /^(?:check|unpack|remove)$/ and $#ARGV == -1 and not $check_every
syntax();
}
+die "Cannot use profile together wtih --ftp-master-rejects.\n" if $OPT_LINTIAN_PROFILE and $ftpmaster_tags;
+# --ftp-master-rejects is implemented in a profile
+$OPT_LINTIAN_PROFILE = 'debian/ftp-master-auto-reject' if $ftpmaster_tags;
+
# }}}
# {{{ Setup Configuration
@@ -524,7 +544,7 @@ unless ($no_conf) {
}
}
-use constant VARS => qw(LAB ARCHIVEDIR DIST AREA ARCH);
+use constant VARS => qw(LAB ARCHIVEDIR DIST AREA ARCH PROFILE);
# read configuration file
if ($LINTIAN_CFG) {
open(CFG, '<', $LINTIAN_CFG)
@@ -623,6 +643,12 @@ foreach (('ROOT', 'CFG', VARS)) {
}
}
+# If we are running the test suite we should ignore
+# user/system profiles.
+if ($ENV{'LINTIAN_INTERNAL_TESTSUITE'}){
+ @prof_inc = ();
+}
+
$verbose = 1 if $debug;
$ENV{'LINTIAN_DEBUG'} = $debug;
@@ -656,7 +682,11 @@ import Lintian::Output qw(:messages);
require Lintian::Command::Simple;
require Lintian::Command;
import Lintian::Command qw(spawn reap);
+require Lintian::Internal::FrontendUtil;
+import Lintian::Internal::FrontendUtil;
require Lintian::ProcessablePool;
+require Lintian::Profile;
+require Lintian::Tag::Info;
require Lintian::Tags;
import Lintian::Tags qw(tag);
@@ -680,20 +710,13 @@ if (defined $experimental_output_opts) {
}
}
+
$Lintian::Output::GLOBAL->verbose($verbose);
$Lintian::Output::GLOBAL->debug($debug);
$Lintian::Output::GLOBAL->quiet($quiet);
$Lintian::Output::GLOBAL->color($opt{'color'});
$Lintian::Output::GLOBAL->showdescription($opt{'info'});
-# Now that we can load the data, process the -F or --ftp-master-rejects
-# option.
-if ($ftpmaster_tags) {
- my $fatal = Lintian::Data->new('output/ftp-master-fatal');
- my $nonfatal = Lintian::Data->new('output/ftp-master-nonfatal');
- $check_tags = join(',', $fatal->all, $nonfatal->all);
-}
-
# Print Debug banner, now that we're finished determining
# the values and have Lintian::Output available
debug_msg(1,
@@ -715,13 +738,40 @@ $TAGS->sources(keys %display_source) if %display_source;
$TAGS->only(split(/,/, $check_tags)) if defined $check_tags;
$TAGS->suppress(keys %suppress_tags) if %suppress_tags;
-# Initialize display level settings.
-for my $level (@display_level) {
- eval { $TAGS->display(@$level) };
- if ($@) {
- my $error = $@;
- $error =~ s/ at .*//;
- die $error, "\n";
+if ($no_profile) {
+ # No profile if we have been given explicit list
+ $LINTIAN_PROFILE = '';
+ # If we are given explicit list, we use that regardless
+ # of show_pedantic/display.
+ $TAGS->respect_display_level(0);
+} else {
+ unless ($LINTIAN_PROFILE){
+ # Time to ask dpkg-vendor for a vendor name
+ $LINTIAN_PROFILE = find_default_profile(@prof_inc, "$LINTIAN_ROOT/profiles");
+ }
+}
+
+if ($LINTIAN_PROFILE) {
+ my $profile = Lintian::Profile->new($LINTIAN_PROFILE,
+ [@prof_inc, "$LINTIAN_ROOT/profiles"]);
+ my @ptags = $profile->tags;
+ my @ign_overrides = $profile->ignored_overrides;
+ my $severities = $profile->severity_changes;
+ v_msg('Using profile ' . $profile->name . '.');
+ $TAGS->only(@ptags) if @ptags;
+ $TAGS->ignore_overrides(@ign_overrides) if @ign_overrides;
+ while ( my ($tagname, $severity) = each(%$severities) ){
+ my $tag = Lintian::Tag::Info->new($tagname);
+ $tag->set_severity($severity);
+ }
+ # Initialize display level settings.
+ for my $level (@display_level) {
+ eval { $TAGS->display(@$level) };
+ if ($@) {
+ my $error = $@;
+ $error =~ s/ at .*//;
+ die $error, "\n";
+ }
}
}
@@ -1177,6 +1227,7 @@ sub set_value {
delete $source->{$field};
}
+
# Given a ref to %collection_info and the path to the collection
# directory, this will load all the collection information into
# %collection_info.
diff --git a/frontend/lintian-info b/frontend/lintian-info
index df4ad6c..c512db1 100755
--- a/frontend/lintian-info
+++ b/frontend/lintian-info
@@ -37,15 +37,25 @@ BEGIN {
# import perl libraries
use lib "$ENV{'LINTIAN_ROOT'}/lib";
+use Lintian::Internal::FrontendUtil;
+use Lintian::Profile;
use Lintian::Tag::Info ();
use Text_utils;
my %already_displayed = ();
-
-my ($annotate, $tags, $help);
+my @proc_inc = (
+ "$ENV{HOME}/.lintian/profiles",
+ '/etc/lintian/profiles',
+ "$ENV{'LINTIAN_ROOT'}/profiles"
+);
+my ($annotate, $tags, $help, $prof);
Getopt::Long::config('bundling', 'no_getopt_compat', 'no_auto_abbrev');
-GetOptions('annotate|a' => \$annotate, 'tags|t' => \$tags, 'help|h' => \$help)
- or die("error parsing options\n");
+GetOptions(
+ 'annotate|a' => \$annotate,
+ 'tags|t' => \$tags,
+ 'help|h' => \$help,
+ 'profile' => \$prof,
+) or die("error parsing options\n");
# help
if ($help) {
@@ -57,11 +67,17 @@ Usage: lintian-info [log-file...] ...
Options:
-a, --annotate display descriptions of tags in Lintian overrides
-t, --tags display tag descriptions
+ --profile X use vendor profile X to determine severities
EOT
exit 0;
}
+unless ($prof) {
+ $prof = find_default_profile(@proc_inc);
+
+}
+load_profile($prof, \@proc_inc);
# If tag mode was specified, read the arguments as tags and display the
# descriptions for each one. (We don't currently display the severity,
@@ -69,12 +85,14 @@ EOT
my $unknown;
if ($tags) {
for my $tag (@ARGV) {
- print "N: $tag\n";
- print "N:\n";
my $info = Lintian::Tag::Info->new($tag);
if ($info) {
+ print $info->code . ": $tag\n";
+ print "N:\n";
print $info->description('text', 'N: ');
} else {
+ print "N: $tag\n";
+ print "N:\n";
print "N: Unknown tag.\n";
$unknown = 1;
}
@@ -120,6 +138,19 @@ while (<>) {
exit 0;
+# load_profile($profname, $proc_inc_ref)
+# Loads the profile called $profname and applies
+# the relevant changes from it.
+sub load_profile{
+ my ($profname, $proc_inc_ref) = @_;
+ my $profile = Lintian::Profile->new($prof, $proc_inc_ref);
+ my $severities = $profile->severity_changes;
+ while ( my ($tagname, $severity) = each(%$severities) ){
+ my $tag = Lintian::Tag::Info->new($tagname);
+ $tag->set_severity($severity);
+ }
+}
+
# Local Variables:
# indent-tabs-mode: t
# cperl-indent-level: 4
diff --git a/lib/Lintian/Internal/FrontendUtil.pm b/lib/Lintian/Internal/FrontendUtil.pm
new file mode 100644
index 0000000..4e3a2b9
--- /dev/null
+++ b/lib/Lintian/Internal/FrontendUtil.pm
@@ -0,0 +1,102 @@
+# -*- perl -*-
+# Lintian::Internal::FrontendUtil -- internal helpers for lintian frontends
+
+# Copyright (C) 2011 Niels Thykier
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+package Lintian::Internal::FrontendUtil;
+use strict;
+use warnings;
+
+use base qw(Exporter);
+use Util;
+
+our @EXPORT = qw(&check_test_feature &find_default_profile);
+
+# Check if we are testing a specific feature
+# - e.g. vendor-libdpkg-perl
+sub check_test_feature{
+ my $env = $ENV{LINTIAN_TEST_FEATURE};
+ return 0 unless $env;
+ foreach my $feat (@_){
+ return 1 if($env =~ m/$feat/);
+ }
+ return 0;
+}
+
+# _find_parent_vendor_dpkg_vendor($vendor)
+#
+# returns the parent vendor using dpkg-vendor
+sub _find_parent_vendor_dpkg_vendor {
+ my ($cur) = @_;
+ my $par;
+ chomp($par = `dpkg-vendor --vendor "$cur" --query Parent`);
+ # dpkg-vendor returns 1 if there is no parent (because the query failed),
+ # which we translate into ''; but other values suggests an internal
+ # dpkg-vendor error.
+ if ( $? ){
+ my $err = ($? >> 8) & 256;
+ fail('dpkg-vendor failed (status: ' . ($? >> 8). ").\n") if $err != 1;
+ return '';
+ }
+ return $par;
+}
+
+# _find_parent_vendor_dpkg_vendor($vendor)
+#
+# returns the parent vendor using libdpkg-perl
+# - assumes Dpkg::Vendor has been required before
+# being invoked.
+sub _find_parent_vendor_libdpkg_perl {
+ my ($cur) = @_;
+ my $info = Dpkg::Vendor::get_vendor_info($cur);
+ # Cannot happen atm, but in case Dpkg::Vendor changes its internals
+ # or our code changes
+ fail("Could not look up the parent vendor of $cur.\n") unless $info;
+ return $info->{'Parent'};
+}
+
+# find_default_profile(@prof_path)
+#
+# locates the default profile - used if no profile was explicitly given.
+sub find_default_profile {
+ my (@prof_path) = @_;
+ my ($vendor, $orig);
+ # CODE-ref to query for the parent vendor
+ # $qparent->("Ubuntu") returns Debian
+ # $qparent->("Debian") returns '' or undef
+ my $qparent;
+ # Use dpkg-vendor if present (unless we are testing our libdpkg-perl code)
+ if(check_path('dpkg-vendor') && !check_test_feature('vendor-libdpkg-perl')){
+ chomp($vendor = `dpkg-vendor --query Vendor`);
+ fail('dpkg-vendor failed (status: ' . ($? >> 8). ").\n") if $?;
+ $qparent = \&_find_parent_vendor_dpkg_vendor;
+ } else {
+ require Dpkg::Vendor;
+ $vendor = Dpkg::Vendor::get_current_vendor();
+ fail("Could not determine the current vendor.\n") unless $vendor;
+ $qparent = \&_find_parent_vendor_libdpkg_perl;
+ }
+ $orig = $vendor;
+ while ($vendor) {
+ my $p;
+ $p = Lintian::Profile->find_profile(lc($vendor), @prof_path);
+ last if $p;
+ $vendor = $qparent->($vendor);
+ }
+ fail("Could not find a profile for vendor $orig") unless $vendor;
+ return lc($vendor);
+}
+
diff --git a/lib/Lintian/Profile.pm b/lib/Lintian/Profile.pm
new file mode 100644
index 0000000..89f538f
--- /dev/null
+++ b/lib/Lintian/Profile.pm
@@ -0,0 +1,419 @@
+# Copyright (C) 2011 Niels Thykier <niels@thykier.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+## Represents a Lintian profile
+package Lintian::Profile;
+
+use base qw(Class::Accessor);
+
+use strict;
+use warnings;
+
+use Util;
+
+=head1 NAME
+
+Lintian::Profile - Profile parser for Lintian
+
+=head1 SYNOPSIS
+
+ my @inc = ("$ENV{'HOME'}/.lintian/profiles/",
+ '/etc/lintian/profiles/',
+ "$ENV{'LINTIAN_ROOT'}/profiles/");
+ # Check if the Ubuntu default profile is present.
+ my $file = Lintian::Profile->find_profile('ubuntu', @inc);
+ # Parse the debian profile (if available)
+ my $profile = Lintian::Profile->new('debian', [@inc]);
+ foreach my $tag ($profile->tags) {
+ print "Enabled tag: $tag\n";
+ }
+ # ...
+
+=head1 DESCRIPTION
+
+
+
+=head1 CLASS METHODS
+
+=over 4
+
+=cut
+
+# maps tag name to tag data.
+my %TAG_MAP = ();
+# maps check name to list of tag names.
+my %CHECK_MAP = ();
+# map of known valid severity allowed by profiles
+my %SEVERITIES = (
+ 'pedantic' => 1,
+ 'wishlist' => 1,
+ 'minor' => 1,
+ 'normal' => 1,
+ 'important' => 1,
+ 'serious' => 1,
+ );
+
+# _load_checks
+#
+# Internal sub to load and fill up %TAG_MAP and %CHECK_MAP
+sub _load_checks {
+ my $root = $ENV{LINTIAN_ROOT} || '/usr/share/lintian';
+ for my $desc (<$root/checks/*.desc>) {
+ my ($header, @tags) = read_dpkg_control($desc);
+ my $cname = $header->{'check-script'};
+ my $tagnames = [];
+ unless ($cname){
+ fail("missing Check-Script field in $desc");
+ }
+ $CHECK_MAP{$cname} = $tagnames;
+ for my $tag (@tags) {
+ unless ($tag->{tag}) {
+ fail("missing Tag field in $desc");
+ }
+ push @$tagnames, $tag->{tag};
+ $tag->{info} = '' unless exists($tag->{info});
+ $tag->{script} = $header->{'check-script'};
+ $TAG_MAP{$tag->{tag}} = $tag;
+ }
+ }
+}
+
+
+=item Lintian::Profile->new($profname, $ppath)
+
+Creates a new profile from the profile located by
+using find_profile($profname, @$ppath). $profname
+is the name of the profile and $ppath is a list
+reference containing the directories to search for
+the profile and (if any) its parents.
+
+=cut
+
+sub new {
+ my ($type, $name, $ppath) = @_;
+ my $profile;
+ fail "Illegal profile name $name\n"
+ if $name =~ m,^/,o or $name =~ m/\./o;
+ _load_checks() unless %TAG_MAP;
+ my $self = {
+ 'parent-map' => {},
+ 'parents' => [],
+ 'profile-path' => $ppath,
+ 'enabled-tags' => {},
+ 'ignored-overrides' => {},
+ 'severity-changes' => {},
+ };
+ $self = bless $self, $type;
+ $profile = $self->find_profile($name);
+ fail "Cannot find profile $name (in " . join(', ', @$ppath).").\n"
+ unless $profile;
+ $self->_read_profile($profile);
+ return $self;
+}
+
+=item $prof->parents
+
+Returns a list ref of the names of its parents, in the order they are
+apply.
+
+Note: This list reference and its contents should not be modified.
+
+=item $prof->name
+
+Returns the name of the profile, which may differ from the name used
+to create this instance of the profile (e.g. due to symlinks).
+
+=cut
+
+Lintian::Profile->mk_ro_accessors (qw(parents name));
+
+=item $prof->tags
+
+Returns the list of tags enabled in this profile.
+
+Note: The contents of this list should not be modified.
+
+=cut
+
+sub tags {
+ my ($self) = @_;
+ return keys %{ $self->{'enabled-tags'} };
+}
+
+=item $prof->severity_changes
+
+Returns a hashref mapping tag names to their altered severity. If an
+enabled tag is not present in this hashref, then it uses its normal
+severity. The altered severity may be the same as the normal
+severity.
+
+Note: This hashref nor its contents should be altered.
+
+=cut
+
+sub severity_changes {
+ my ($self) = @_;
+ return $self->{'severity-changes'};
+}
+
+=item $prof->ignored_overrides
+
+List of tags that has been marked as non-overridable.
+
+Note: This list nor its contents should be modified.
+
+=cut
+
+sub ignored_overrides {
+ my ($self) = @_;
+ return keys %{ $self->{'ignored-overrides'} };
+}
+
+=item Lintian::Profile->find_profile($pname, @dirs), $prof->find_profile($pname[, @dirs])
+
+This can both be used as a static or as an instance method. If used
+as an instance method, the @dirs argument may be omitted.
+
+Finds a profile called $pname in the search directories (see below)
+and returns the path to it. If $pname does not contain a slash, then
+it will look for a profile called "$pname/main" instead of $pname.
+
+Returns a non-truth value if the profile could not be found. $pname
+cannot contain any dots.
+
+Search Dirs: For the static call, only @dirs are considered. For the
+instance method @dirs is augmented with the search dirs present when
+the object was created.
+
+=cut
+
+sub find_profile {
+ my ($self, $pname, @dirs) = @_;
+ my $pfile;
+ fail "$pname is not a valid profile name\n" if $pname =~ m/\./o;
+ # Allow @dirs to override the default path for this profile-search
+ if (ref $self) {
+ push @dirs, @{ $self->{'profile-path'} } if defined $self->{'profile-path'};
+ }
+ # $vendor is short for $vendor/main
+ $pname = "$pname/main" unless $pname =~ m,/,o;
+ $pfile = "$pname.profile";
+ foreach my $path (@dirs){
+ return "$path/$pfile" if -e "$path/$pfile";
+ }
+ return '';
+}
+
+
+# $self->_read_profile($pfile)
+#
+# Parses the profile stored in the file $pfile; if this method returns
+# normally, the profile will have been parsed successfully.
+sub _read_profile {
+ my ($self, $pfile) = @_;
+ my @pdata;
+ my $pheader;
+ my $pmap = $self->{'parent-map'};
+ my $pname;
+ open(my $fd, '<', $pfile) or fail "$pfile: $!";
+ @pdata = parse_dpkg_control($fd, 0);
+ close $fd;
+ $pheader = shift @pdata;
+ fail "Profile field is missing from $pfile."
+ unless defined $pheader && $pheader->{'profile'};
+ $pname = $pheader->{'profile'};
+ fail "Invalid Profile field in $pfile.\n"
+ if $pname =~ m,^/,o or $pname =~ m/\./o;
+ $pmap->{$pname} = 0; # Mark as being loaded.
+ $self->{'name'} = $pname unless exists $self->{'name'};
+ if (exists $pheader->{'extends'} ){
+ my $parent = $pheader->{'extends'};
+ my $plist = $self->{'parents'};
+ my $parentf;
+ fail "Invalid Extends field in $pfile.\n"
+ unless $parent && $parent !~ m/\./o;
+ fail "Recursive definition of $parent.\n"
+ if exists $pmap->{$parent};
+ $parentf = $self->find_profile($parent);
+ fail "Cannot find $parent, which $pname extends.\n"
+ unless $parentf;
+ $self->_read_profile($parentf);
+ push @$plist, $parent;
+ }
+ $self->_read_profile_tags($pname, $pheader);
+ if (@pdata){
+ my $i = 2; # section counter
+ foreach my $psection (@pdata){
+ $self->_read_profile_section($pname, $psection, $i++);
+ }
+ }
+}
+
+
+# $self->_read_profile_section($pname, $section, $sno)
+#
+# Parses and applies the effects of $section (a paragraph
+# in the profile). $pname is the name of the profile and
+# $no is section number (both of these are only used for
+# error reporting).
+sub _read_profile_section {
+ my ($self, $pname, $section, $sno) = @_;
+ my @tags = $self->_split_comma_sep_field($section->{'tags'});
+ my $overridable = $self->_parse_boolean($section->{'overridable'}, -1, $pname, $sno);
+ my $severity = $section->{'severity'}//'';
+ my $ignore_map = $self->{'ignored-overrides'};
+ my $sev_map = $self->{'severity-changes'};
+ fail "Profile \"$pname\" is missing Tags field (or it is empty) in section $sno." unless @tags;
+ fail "Profile \"$pname\" contains invalid severity \"$severity\" in section $sno."
+ if $severity && !$SEVERITIES{$severity};
+ foreach my $tag (@tags) {
+ fail "Unknown check $tag in $pname (section $sno)\n" unless exists $TAG_MAP{$tag};
+ $sev_map->{$tag} = $severity if $severity;
+ if ( $overridable != -1 ) {
+ if ($overridable) {
+ delete $ignore_map->{$tag};
+ } else {
+ $ignore_map->{$tag} = 1;
+ }
+ }
+ }
+}
+
+# $self->_read_profile_tags($pname, $pheader)
+#
+# Interprets the {dis,en}able-tags{,-from-chcek} fields from
+# the profile header $pheader. $pname is the name of the
+# profile (used for error reporting).
+#
+# If it returns, the enabled tags will be updated to reflect
+# the tags enabled/disabled by this profile (but not its
+# parents).
+sub _read_profile_tags{
+ my ($self, $pname, $pheader) = @_;
+ $self->_check_duplicates($pname, $pheader, 'enable-tags-from-check', 'disable-tags-from-check');
+ $self->_check_duplicates($pname, $pheader, 'enable-tags', 'disable-tags');
+ my $tags_from_check_sub = sub {
+ my ($field, $check) = @_;
+ fail "Unknown check $check in $pname\n" unless exists $CHECK_MAP{$check};
+ return @{$CHECK_MAP{$check}};
+ };
+ my $tag_sub = sub {
+ my ($field, $tag) = @_;
+ fail "Unknown check $tag in $pname\n" unless exists $TAG_MAP{$tag};
+ return $tag;
+ };
+ $self->_enable_tags_from_field($pname, $pheader, 'enable-tags-from-check', $tags_from_check_sub, 1);
+ $self->_enable_tags_from_field($pname, $pheader, 'disable-tags-from-check', $tags_from_check_sub, 0);
+ $self->_enable_tags_from_field($pname, $pheader, 'enable-tags', $tag_sub, 1);
+ $self->_enable_tags_from_field($pname, $pheader, 'disable-tags', $tag_sub, 0);
+}
+
+# $self->_enable_tags_from_field($pname, $pheader, $field, $code, $enable)
+#
+# Parse $field in $pheader as a comma separated list of items; these items are then
+# passed to $code, that must returns a list of tags. If $enable is a truth value
+# these tags are enabled in the profile, otherwise they are disabled.
+sub _enable_tags_from_field {
+ my ($self, $pname, $pheader, $field, $code, $enable) = @_;
+ my $tags = $self->{'enabled-tags'};
+ return unless $pheader->{$field};
+ foreach my $tag (map { $code->($field, $_) } $self->_split_comma_sep_field($pheader->{$field})){
+ if($enable) {
+ $tags->{$tag} = 1;
+ } else {
+ delete $tags->{$tag};
+ }
+ }
+}
+
+
+# $self->_check_duplicates($name, $map, @fields)
+#
+# Checks the @fields in $map for duplicate values. The
+# values are parsed as comma-separated lists. The same
+# entry in these lists are not allowed twice, regardless
+# of they appear twice in the same field or once in two
+# different fields of @fields.
+#
+#
+sub _check_duplicates{
+ my ($self, $name, $map, @fields) = @_;
+ my %dupmap;
+ foreach my $field (@fields) {
+ next unless exists $map->{$field};
+ foreach my $element (split m/\s*+,\s*+/o, $map->{$field}){
+ if (exists $dupmap{$element}){
+ my $other = $dupmap{$element};
+ fail "$element appears in both $field and $other in $name.\n"
+ unless $other eq $field;
+ fail "$element appears twice in $field in $name.\n";
+ }
+ $dupmap{$element} = $field;
+ }
+ }
+}
+
+# $self->_parse_boolean($bool, $def, $pname, $sno);
+#
+# Parse $bool as a string representing a bool; if undefined return $def.
+# $pname and $sno are the Profile name and section number - used for
+# error reporting.
+sub _parse_boolean {
+ my ($self, $bool, $def, $pname, $sno) = @_;
+ return $def unless defined $bool;
+ $bool = lc $bool;
+ return 1 if $bool eq 'yes' || $bool eq 'true' ||
+ ($bool =~ m/^\d++$/o && $bool != 0);
+ return 0 if $bool eq 'no' || $bool eq 'false' ||
+ ($bool =~ m/^\d++$/o && $bool == 0);
+ fail "\"$bool\" is not a boolean value in $pname (section $sno).";
+}
+
+# $self->_split_comma_sep_field($data)
+#
+# Split $data as a comma-separated list of items (whitespace will
+# be ignored).
+sub _split_comma_sep_field {
+ my ($self, $data) = @_;
+ return () unless defined $data;
+ # remove trailing and leading white-space
+ $data =~ s/^\s++//o;
+ $data =~ s/\s++$//o;
+ return split m/\s*,\s*/o, $data;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Niels Thykier <niels@thykier.net> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=4 sw=4 et
+
diff --git a/lib/Lintian/Tag/Info.pm b/lib/Lintian/Tag/Info.pm
index 405e75c..c8a0a65 100644
--- a/lib/Lintian/Tag/Info.pm
+++ b/lib/Lintian/Tag/Info.pm
@@ -106,6 +106,7 @@ sub _load_tag_data {
$tag->{info} = '' unless exists($tag->{info});
$tag->{script} = $header->{'check-script'};
$tag->{'script-type'} = $header->{'type'};
+ $tag->{'effective-severity'} = $tag->{severity};
$INFO{$tag->{tag}} = $tag;
}
}
@@ -156,7 +157,7 @@ separately.
sub code {
my ($self) = @_;
- return $CODES{$self->{severity}}{$self->{certainty}};
+ return $CODES{$self->{'effective-severity'}}{$self->{certainty}};
}
=item description([FORMAT [, INDENT]])
@@ -315,17 +316,33 @@ sub experimental {
return ($self->{experimental} and $self->{experimental} eq 'yes');
}
-=item severity()
+=item severity([$real])
-Returns the severity of the tag.
+Returns the severity of the tag; if $real is a truth value
+the real (original) severity is returned, otherwise the
+effective severity is returned.
+
+See set_severity()
=cut
sub severity {
- my ($self) = @_;
+ my ($self, $real) = @_;
+ return $self->{'effective-severity'} unless $real;
return $self->{severity};
}
+=item set_severity($severity)
+
+Modifies the effective severity of the tag.
+
+=cut
+
+sub set_severity{
+ my ($self, $sev) = @_;
+ $self->{'effective-severity'} = $sev;
+}
+
=item script()
Returns the check script corresponding to this tag.
diff --git a/lib/Lintian/Tags.pm b/lib/Lintian/Tags.pm
index 8bd6f3b..655f92d 100644
--- a/lib/Lintian/Tags.pm
+++ b/lib/Lintian/Tags.pm
@@ -153,7 +153,9 @@ sub new {
},
display_source => {},
files => {},
+ ignored_overrides => {},
only_issue => {},
+ respect_display => 1,
show_experimental => 0,
show_overrides => 0,
show_pedantic => 0,
@@ -535,6 +537,7 @@ file cannot be opened.
sub file_overrides {
my ($self, $overrides) = @_;
+ my $ignored = $self->{ignored_overrides};
unless (defined $self->{current}) {
die 'no current file when adding overrides';
}
@@ -579,6 +582,7 @@ sub file_overrides {
}
next unless $found;
}
+ next if $ignored->{$tag};
$extra = '' unless defined $extra;
$info->{overrides}{$tag}{$extra} = 0;
} else {
@@ -677,6 +681,13 @@ sub displayed {
my ($self, $tag) = @_;
my $info = Lintian::Tag::Info->new($tag);
return 0 if ($info->experimental and not $self->{show_experimental});
+ my $only = $self->{only_issue};
+ if (%$only) {
+ return 0 unless $only->{$tag};
+ return 1 unless $self->{respect_display};
+ } else {
+ return 0 if $self->suppressed($tag);
+ }
my $severity = $info->severity;
my $certainty = $info->certainty;
@@ -727,6 +738,45 @@ sub suppressed {
return;
}
+=item ignore_overrides(TAG[, ...])
+
+Ignores all future overrides for all tags given as arguments.
+
+=cut
+
+sub ignore_overrides {
+ my ($self, @tags) = @_;
+ my $ignored = $self->{ignored_overrides};
+ foreach my $tag (@tags){
+ $ignored->{$tag} = 1;
+ }
+ return 1;
+}
+
+=item respect_display_level([BOOL])
+
+Whether or not the display level should be considered for
+tags that can be emitted.
+
+Calling this with a defined non-truth value and calling
+only(TAG) will emit all of the tags passed to only(),
+regardless of what is passed to show_pedantic() and
+display() etc.
+
+Returns the old value.
+
+Note: This does not effect suppressed tags, which will
+always be suppressed regardless.
+
+=cut
+
+sub respect_display_level{
+ my ($self, $val) = @_;
+ my $old = $self->{respect_display};
+ $self->{respect_display} = $val if defined $val;
+ return $old;
+}
+
=back
=head1 AUTHOR
diff --git a/man/lintian-info.pod b/man/lintian-info.pod
index c44dec0..b13fe74 100644
--- a/man/lintian-info.pod
+++ b/man/lintian-info.pod
@@ -60,6 +60,18 @@ about that tag.
Display usage information and exit.
+=item B<--profile> prof
+
+Use the severities from the vendor profile prof when displaying tags.
+If the profile name does not contain a slash, the default profile for
+than vendor is chosen.
+
+If not specified, B<lintian-info> loads the best profile for the
+current vendor.
+
+Please Refer to the Lintian User Manual for the full documentation of
+profiles.
+
=item B<-t>, B<--tags>
Rather than treating them as log file names, treat any command-line
diff --git a/man/lintian.pod.in b/man/lintian.pod.in
index 069523e..76be2c4 100644
--- a/man/lintian.pod.in
+++ b/man/lintian.pod.in
@@ -67,13 +67,17 @@ Run only the specified checks. You can either specify the name of the
check script or the abbreviation. For details, see the L</CHECKS> section
below.
+Note: This option skips profile loading, even if B<--profile> was passed.
+
=item B<-F>, B<--ftp-master-rejects>
Run only the checks that issue tags that result in automatic rejects
from the Debian upload queue. The list of such tags is refreshed with
each Lintian release, so may be slightly out of date if it has changed
-recently. This option does not, as yet, ignore overrides for fatal
-tags for which overrides aren't allowed.
+recently.
+
+This is implemented via a profile and thus this option cannot be used
+together with B<--profile>.
=item B<-r>, B<--remove>
@@ -94,6 +98,8 @@ Run only the checks that issue the requested tags. The tests for
other tags within the check scripts will be run but the tags will not
be issued.
+Note: This option skips profile loading, even if B<--profile> was passed.
+
=item B<--tags-from-file> filename
Same functionality as B<--tags>, but read the list of tags from a
@@ -101,6 +107,8 @@ file. Blank lines and lines beginning with # are ignored. All other
lines are taken to be tag names or comma-separated lists of tag names
to (potentially) issue.
+Note: This option skips profile loading, even if B<--profile> was passed.
+
=item B<-u>, B<--unpack>
Unpacks the package will all collections. See the L</COLLECTION>
@@ -274,6 +282,18 @@ tags is probably not worth the effort.
This option overrides the B<pedantic> variable in the configuration
file.
+=item B<--profile> vendor[/prof]
+
+Use the profile from vendor (or the profile with that name). If the
+profile name does not contain a slash, the default profile for than
+vendor is chosen.
+
+If not specified, B<lintian> loads the best profile for the current
+vendor.
+
+Please Refer to the Lintian User Manual for the full documentation of
+profiles.
+
=item B<--show-overrides>
Output tags that have been overridden.
diff --git a/private/generate-profiles.pl b/private/generate-profiles.pl
new file mode 100755
index 0000000..4915527
--- /dev/null
+++ b/private/generate-profiles.pl
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+# Generates profiles for the Debian vendor
+# - Remember to add new profiles to d/rules under profiles
+
+use strict;
+use warnings;
+
+use constant LINE_LENGTH => 80;
+use constant FIELD_ORDER => (
+ 'Extends',
+ 'Enable-Tags-From-Check',
+ 'Disable-Tags-From-Check',
+ 'Enable-Tags',
+ 'Disable-Tags',
+ );
+use constant PARAGRAPH_ORDER => ( 'Overridable', 'Severity' );
+
+use lib "$ENV{LINTIAN_ROOT}/lib";
+use Lintian::Data;
+use Util;
+
+my $root = $ENV{LINTIAN_ROOT};
+my @dirs = ('profiles/debian');
+my @checks;
+my @fatal;
+my @nonfatal;
+
+foreach my $check (<$root/checks/*.desc>){
+ my ($header, undef) = read_dpkg_control($check);
+ my $cname = $header->{'check-script'};
+ fail "$check missing check-script\n" unless defined $cname;
+ push @checks, $cname;
+}
+
+@fatal = Lintian::Data->new('output/ftp-master-fatal')->all;
+@nonfatal = Lintian::Data->new('output/ftp-master-nonfatal')->all;
+
+foreach my $dir (@dirs) {
+ mkdir $dir or fail "mkdir $dir: $!" unless -d $dir;
+}
+
+generate_profile('debian/main', {
+ 'Extends' => 'debian/ftp-master-auto-reject',
+ 'Enable-Tags-From-Check' => \@checks,
+ });
+
+generate_profile('debian/ftp-master-auto-reject', {
+ 'Enable-Tags' => [@fatal, @nonfatal],
+ },
+ { 'Tags' => \@fatal,
+ 'Overridable' => 'no',
+ });
+
+exit 0;
+
+
+sub generate_profile {
+ my ($profile, $main, @other) = @_;
+ my $filename = "profiles/$profile.profile";
+ open(my $fd, '>', $filename) or die "$filename: $!";
+ print $fd "# This profile is auto-generated\n";
+ print $fd "Profile: $profile\n";
+ foreach my $f (FIELD_ORDER) {
+ my $val = $main->{$f};
+ next unless defined $val;
+ if ($f eq 'Extends') {
+ format_field($fd, $f, $val);
+ } else {
+ format_field($fd, $f, sort @$val);
+ }
+ }
+ print $fd "\n";
+ foreach my $para (@other) {
+ format_field($fd, 'Tags', sort @{ $para->{'Tags'} });
+ foreach my $f (PARAGRAPH_ORDER) {
+ my $val = $para->{$f};
+ next unless defined $val;
+ print $fd "$f: $val\n";
+ }
+ print $fd "\n";
+ }
+ close($fd) or die "$filename: $!";
+}
+
+sub format_field {
+ my ($fd, $field, @elements) = @_;
+ my $llen = length($field) + 2;
+ my $first = shift @elements;
+ print $fd "$field: $first";
+ foreach my $el (@elements){
+ my $ellen = length $el;
+ if ($llen + $ellen + 2 <= LINE_LENGTH || $llen <= 2){
+ print $fd ", $el";
+ $llen += $ellen + 2;
+ } else {
+ print $fd ",\n $el";
+ $llen = $ellen + 1;
+ }
+ }
+ print $fd "\n";
+}
+
diff --git a/private/runtests b/private/runtests
index 7d98eae..f6fe04b 100755
--- a/private/runtests
+++ b/private/runtests
@@ -18,12 +18,16 @@ fi
LANG="en_US.UTF-8"
LC_COLLATE="C"
LINTIAN_ROOT=""
+LINTIAN_PROFILE=debian
+LINTIAN_INTERNAL_TESTSUITE=1
LOCPATH="$(pwd)/debian/test.locale"
NO_PKG_MANGLE=true
export LANG
export LC_COLLCATE
export LINTIAN_ROOT
+export LINTIAN_PROFILE
+export LINTIAN_INTERNAL_TESTSUITE
export LOCPATH
export NO_PKG_MANGLE
diff --git a/profiles/debian/ftp-master-auto-reject.profile b/profiles/debian/ftp-master-auto-reject.profile
new file mode 100644
index 0000000..104f256
--- /dev/null
+++ b/profiles/debian/ftp-master-auto-reject.profile
@@ -0,0 +1,69 @@
+# This profile is auto-generated
+Profile: debian/ftp-master-auto-reject
+Enable-Tags: FSSTND-dir-in-usr, FSSTND-dir-in-var, arch-dependent-file-in-usr-share,
+ arch-independent-package-contains-binary-or-object, bad-package-name,
+ bad-relation, bad-version-number, binary-file-compressed-with-upx,
+ binary-in-etc, binary-or-shlib-defines-rpath, binary-with-bad-dynamic-table,
+ build-info-in-binary-control-file-section, control-file-has-bad-owner,
+ control-file-has-bad-permissions, control-interpreter-in-usr-local,
+ control-interpreter-without-depends,
+ copyright-contains-dh_make-todo-boilerplate, copyright-file-compressed,
+ copyright-file-is-symlink, copyright-refers-to-incorrect-directory,
+ copyright-refers-to-old-directory,
+ debian-control-file-uses-obsolete-national-encoding,
+ debian-control-with-duplicate-fields, debian-rules-missing-required-target,
+ debian-rules-not-a-makefile, description-is-dh_make-template,
+ description-synopsis-is-empty, dir-or-file-in-mnt, dir-or-file-in-opt,
+ dir-or-file-in-srv, dir-or-file-in-tmp, dir-or-file-in-var-www,
+ embedded-library, extended-description-is-empty,
+ file-in-etc-not-marked-as-conffile, file-in-usr-marked-as-conffile,
+ forbidden-postrm-interpreter, install-info-used-in-maintainer-script,
+ library-in-debug-or-profile-should-not-be-stripped, magic-arch-in-arch-list,
+ maintainer-address-is-on-localhost, maintainer-address-malformed,
+ maintainer-address-missing, maintainer-name-missing,
+ maintainer-script-removes-device-files, malformed-deb-archive,
+ missing-build-dependency, missing-dependency-on-libc,
+ missing-dependency-on-perlapi, missing-pre-dependency-on-multiarch-support,
+ mknod-in-maintainer-script, no-architecture-field, no-copyright-file,
+ no-maintainer-field, no-package-name, no-shlibs-control-file, no-source-field,
+ no-version-field, non-etc-file-marked-as-conffile, not-allowed-control-file,
+ old-style-copyright-file, package-contains-ancient-file,
+ package-contains-info-dir-file, package-has-no-description,
+ package-installs-python-bytecode, package-not-lowercase,
+ package-uses-local-diversion, preinst-interpreter-without-predepends,
+ section-is-dh_make-template, source-field-does-not-match-pkg-name,
+ statically-linked-binary, symlink-has-too-many-up-segments,
+ too-many-architectures, uploader-address-is-on-localhost,
+ uploader-address-malformed, uploader-name-missing,
+ usr-share-doc-symlink-to-foreign-package,
+ usr-share-doc-symlink-without-dependency, wrong-file-owner-uid-or-gid
+
+Tags: FSSTND-dir-in-usr, FSSTND-dir-in-var, bad-package-name, bad-relation, bad-version-number,
+ binary-file-compressed-with-upx, binary-in-etc,
+ build-info-in-binary-control-file-section, control-file-has-bad-owner,
+ control-file-has-bad-permissions, control-interpreter-in-usr-local,
+ copyright-file-compressed, copyright-file-is-symlink,
+ copyright-refers-to-incorrect-directory, copyright-refers-to-old-directory,
+ debian-control-file-uses-obsolete-national-encoding,
+ debian-control-with-duplicate-fields, debian-rules-missing-required-target,
+ debian-rules-not-a-makefile, description-is-dh_make-template,
+ description-synopsis-is-empty, dir-or-file-in-mnt, dir-or-file-in-opt,
+ dir-or-file-in-srv, dir-or-file-in-tmp, extended-description-is-empty,
+ file-in-etc-not-marked-as-conffile, file-in-usr-marked-as-conffile,
+ forbidden-postrm-interpreter,
+ library-in-debug-or-profile-should-not-be-stripped, magic-arch-in-arch-list,
+ maintainer-address-is-on-localhost, maintainer-address-malformed,
+ maintainer-address-missing, maintainer-name-missing,
+ maintainer-script-removes-device-files, malformed-deb-archive,
+ missing-dependency-on-perlapi, no-architecture-field, no-copyright-file,
+ no-maintainer-field, no-package-name, no-source-field, no-version-field,
+ not-allowed-control-file, old-style-copyright-file,
+ package-contains-ancient-file, package-has-no-description,
+ package-installs-python-bytecode, package-not-lowercase,
+ package-uses-local-diversion, section-is-dh_make-template,
+ source-field-does-not-match-pkg-name, symlink-has-too-many-up-segments,
+ too-many-architectures, uploader-address-is-on-localhost,
+ uploader-address-malformed, uploader-name-missing,
+ usr-share-doc-symlink-to-foreign-package
+Overridable: no
+
diff --git a/profiles/debian/main.profile b/profiles/debian/main.profile
new file mode 100644
index 0000000..c8a9b1f
--- /dev/null
+++ b/profiles/debian/main.profile
@@ -0,0 +1,11 @@
+# This profile is auto-generated
+Profile: debian/main
+Extends: debian/ftp-master-auto-reject
+Enable-Tags-From-Check: binaries, changelog-file, changes-file, circular-deps, conffiles,
+ control-file, control-files, copyright-file, cruft, deb-format, debconf,
+ debhelper, debian-readme, debian-source-dir, description, duplicate-files,
+ etcfiles, fields, files, huge-usr-share, infofiles, init.d, java, lintian,
+ manpages, md5sums, menu-format, menus, nmu, ocaml, patch-systems, po-debconf,
+ rules, scripts, shared-libs, standards-version, symlinks, version-substvars,
+ watch-file
+
diff --git a/profiles/ubuntu/main.profile b/profiles/ubuntu/main.profile
new file mode 100644
index 0000000..0b79f5d
--- /dev/null
+++ b/profiles/ubuntu/main.profile
@@ -0,0 +1,4 @@
+# The default profile for Ubuntu and derivatives thereof.
+Profile: ubuntu/main
+Extends: debian/main
+Disable-Tag: debian-changelog-file-is-a-symlink
diff --git a/t/runtests b/t/runtests
index 22768dc..bc2b388 100755
--- a/t/runtests
+++ b/t/runtests
@@ -555,6 +555,7 @@ sub test_package {
my $opts = { err => "$rundir/tags.$pkg", fail => 'never' };
my $status;
unshift(@options, '--allow-root', '--no-cfg');
+ unshift(@options, '--profile', $testdata->{profile}) if $testdata->{profile};
if ($testdata->{sort}) {
$status = spawn($opts, [ $LINTIAN, @options, $file ], '|', [ 'sort' ]);
} else {
diff --git a/t/scripts/pod-coverage.t b/t/scripts/pod-coverage.t
index d959ab2..b0bb132 100755
--- a/t/scripts/pod-coverage.t
+++ b/t/scripts/pod-coverage.t
@@ -20,6 +20,7 @@ our %MODULES =
'Lintian::Data' => [],
'Lintian::DepMap' => [],
'Lintian::DepMap::Properties' => [],
+ 'Lintian::Profile' => [],
'Lintian::Processable' => [],
'Lintian::ProcessableGroup' => [],
'Lintian::ProcessablePool' => [],
diff --git a/t/scripts/profiles-coverage.t b/t/scripts/profiles-coverage.t
new file mode 100755
index 0000000..5d81c2c
--- /dev/null
+++ b/t/scripts/profiles-coverage.t
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+
+# Test for complete coverage of tags in profiles
+# - side-effect, test that all tags and checks
+# in the profiles are valid.
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Lintian::Tag::Info;
+use File::Find;
+require Util; # Test::More (also) exports fail
+
+my $root = $ENV{'LINTIAN_ROOT'};
+my @profiles;
+my %CHECKS;
+my %TAGS;
+
+foreach my $desc (<$root/checks/*.desc>) {
+ my ($header, @tags) = Util::read_dpkg_control($desc);
+ my $list = [];
+ unless ($header->{'check-script'}) {
+ fail("missing Check-Script field in $desc");
+ }
+ $CHECKS{$header->{'check-script'}} = $list;
+ for my $tag (@tags) {
+ unless ($tag->{tag}) {
+ fail("missing Tag field in $desc");
+ }
+ push @$list, $tag->{tag};
+ $TAGS{$tag->{tag}} = 0;
+ }
+}
+
+plan tests => scalar (keys %TAGS);
+
+File::Find::find(\&prof_wanted, "$root/profiles");
+
+foreach my $tag (sort keys %TAGS){
+ cmp_ok($TAGS{$tag}, '>', 0, $tag);
+}
+
+exit 0;
+
+## SUBS ##
+
+sub parse_profile {
+ my ($profile) = @_;
+ my ($header, @section) = Util::read_dpkg_control($profile);
+ my $en_checks = $header->{'enable-tags-from-check'}//'';
+ my $dis_checks = $header->{'disable-tags-from-check'}//'';
+ my $en_tag = $header->{'enable-tags'}//'';
+ my $dis_tag = $header->{'disable-tags'}//'';
+ foreach my $check (split m/\s*+,\s*+/o, $en_checks){
+ die "Unknown check ($check) in $profile.\n" unless $CHECKS{$check};
+ foreach my $tag (@{$CHECKS{$check}}){
+ $TAGS{$tag}++;
+ }
+ }
+ foreach my $tag (split m/\s*+,\s*+/o, $en_tag){
+ die "Unknown tag ($tag) in $profile.\n" unless exists $TAGS{$tag};
+ $TAGS{$tag}++;
+ }
+
+ # Check for unknown checks in the other fields
+ foreach my $check (split m/\s*+,\s*+/o, $dis_checks){
+ die "Unknown check in $profile.\n" unless $CHECKS{$check};
+ }
+ foreach my $tag (split m/\s*+,\s*+/o, $dis_tag){
+ die "Unknown tag in $profile.\n" unless exists $TAGS{$tag};
+ }
+ # ... and other fields
+ foreach my $sect (@section){
+ foreach my $tag (split m/\s*+,\s*+/o, $sect->{'tags'}//''){
+ die "Unknown tag ($tag) in $profile.\n" unless exists $TAGS{$tag};
+ }
+ }
+}
+
+sub prof_wanted {
+ parse_profile($_) if -f && m/\.profile$/o;
+}
+
diff --git a/t/tests/README b/t/tests/README
index 045e980..3668aaa 100644
--- a/t/tests/README
+++ b/t/tests/README
@@ -88,6 +88,13 @@ the test case as a non-native package, add:
to the .desc file. You will also want to change the version number to
be non-native unless you're testing a mismatch.
+By default all tests are run with the default Lintian profile. If a
+different profile is needed it can be specified via:
+
+ Profile: test/profile
+
+The value will be passed to Lintian via the --profile parameter.
+
There are times when one wants to add a test for something that needs
to be done. To mark it as such, preventing the test suite from
failing, use:
diff --git a/t/tests/lintian-ftp-rejects/desc b/t/tests/lintian-ftp-rejects/desc
index f369105..1e26f0d 100644
--- a/t/tests/lintian-ftp-rejects/desc
+++ b/t/tests/lintian-ftp-rejects/desc
@@ -1,6 +1,6 @@
Testname: lintian-ftp-rejects
Sequence: 2500
Version: 1.0
-Options: -F
+Profile: debian/ftp-master-auto-reject
Description: Test Lintian --ftp-master-rejects handling
Test-For: dir-or-file-in-opt
diff --git a/t/tests/lintian-overrides/debian/debian/overrides b/t/tests/lintian-overrides/debian/debian/overrides
index 1250c8c..fcf6f7b 100644
--- a/t/tests/lintian-overrides/debian/debian/overrides
+++ b/t/tests/lintian-overrides/debian/debian/overrides
@@ -1,2 +1,2 @@
lintian-override:
-lintian-overrides: say hi :)
+lintian-overrides: copyright-contains-dh_make-todo-boilerplate foo-bar-extra
diff --git a/t/tests/lintian-overrides/tags b/t/tests/lintian-overrides/tags
index dd04745..e0d314d 100644
--- a/t/tests/lintian-overrides/tags
+++ b/t/tests/lintian-overrides/tags
@@ -1,2 +1,2 @@
E: lintian-overrides: malformed-override lintian-override:
-I: lintian-overrides: unused-override say hi :)
+I: lintian-overrides: unused-override copyright-contains-dh_make-todo-boilerplate foo-bar-extra
--
Debian package checker
Reply to: