# control-file -- lintian check script -*- perl -*- # # Copyright (C) 2004 Marc Brockschmidt # # 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. package Lintian::control_file; use strict; use warnings; use Lintian::Data (); use Lintian::Relation (); use Lintian::Tags qw(tag); use Util; # The list of libc packages, used for checking for a hard-coded dependency # rather than using ${shlibs:Depends}. our @LIBCS = qw(libc6 libc6.1 libc0.1 libc0.3); my $src_fields = Lintian::Data->new('common/source-fields'); sub run { my $pkg = shift; my $type = shift; my $info = shift; my $dcontrol = $info->debfiles('control'); if (-l $dcontrol) { tag 'debian-control-file-is-a-symlink'; } # check that control is UTF-8 encoded my $line = file_is_encoded_in_non_utf8($dcontrol, $type, $pkg); if ($line) { tag 'debian-control-file-uses-obsolete-national-encoding', "at line $line" } # Check that each field is only used once: my $seen_fields = {}; my $seen_vcs_comment = 0; open (CONTROL, '<', $dcontrol) or fail "Couldn't read debfiles/control: $!"; while () { s/\s*\n$//; if (m,^\# \s* Vcs-(?:Git|Browser): \s* (?:git|http)://git\.debian\.org/(?:\?p=)?collab-maint/\.git,ox) { # Emit it only once per package tag 'control-file-contains-dh_make-vcs-comment' unless $seen_vcs_comment++; next; } next if /^\#/; #Reset seen_fields if we enter a new section: $seen_fields = {} if $_ eq ''; #line with field: if (/^(\S+):/) { my $field = lc ($1); if ($seen_fields->{$field}) { tag 'debian-control-with-duplicate-fields', "$field: $$seen_fields{$field}, $."; } $seen_fields->{$field} = $.; if ($field =~ /^xs-vcs-/) { my $base = $field; $base =~ s/^xs-//; tag 'xs-vcs-header-in-debian-control', $field if $src_fields->known($base); } if ($field eq 'xc-package-type') { tag 'xc-package-type-in-debian-control', "line $."; } unless (/^\S+: \S/ || /^\S+:$/) { tag 'debian-control-has-unusual-field-spacing', "line $."; } } } close CONTROL; my ($header, @binary_controls); eval { ($header, @binary_controls) = read_dpkg_control($dcontrol); }; if ($@) { chomp $@; $@ =~ s/^internal error: //; $@ =~ s/^syntax error in //; tag 'syntax-error-in-control-file', "debian/control: $@"; return; } for my $binary_control (@binary_controls) { tag 'build-info-in-binary-control-file-section', 'Package '.$binary_control->{'package'} if ($binary_control->{'build-depends'} || $binary_control->{'build-depends-indep'} || $binary_control->{'build-conflicts'} || $binary_control->{'build-conflicts-indep'}); for my $field (keys %$binary_control) { tag 'binary-control-field-duplicates-source', "field \"$field\" in package ".$binary_control->{'package'}, if ($header->{$field} && $binary_control->{$field} eq $header->{$field}); } } # Check that fields which should be comma-separated or pipe-separated have # separators. Places where this tends to cause problems are with wrapped # lines such as: # # Depends: foo, bar # baz # # or with substvars. If two substvars aren't separated by a comma, but at # least one of them expands to an empty string, there will be a lurking bug. # The result will be syntactically correct, but as soon as both expand into # something non-empty, there will be a syntax error. # # The architecture list can contain things that look like packages separated # by spaces, so we have to remove any architecture restrictions first. This # unfortunately distorts our report a little, but hopefully not too much. # # Also check for < and > relations. dpkg-gencontrol warns about them and then # transforms them in the output to <= and >=, but it's easy to miss the error # message. Similarly, check for duplicates, which dpkg-source eliminates. for my $control ($header, @binary_controls) { for my $field (qw(pre-depends depends recommends suggests breaks conflicts provides replaces enhances build-depends build-depends-indep build-conflicts build-conflicts-indep)) { next unless $control->{$field}; my $relation = Lintian::Relation->new($control->{$field}); my @dups = $relation->duplicates; for my $dup (@dups) { tag 'duplicate-in-relation-field', 'in', ($control->{source} ? 'source' : $control->{package}), "$field:", join(', ', @$dup); } my $value = $control->{$field}; $value =~ s/\n(\s)/$1/g; $value =~ s/\[[^\]]*\]//g; if ($value =~ /(?:^|\s) ( (?:\w[^\s,|\$\(]+|\$\{\S+:Depends\})\s* (?:\([^\)]*\)\s*)? ) \s+ ( (?:\w[^\s,|\$\(]+|\$\{\S+:Depends\})\s* (?:\([^\)]*\)\s*)? )/x) { my ($prev, $next) = ($1, $2); for ($prev, $next) { s/\s+$//; } tag 'missing-separator-between-items', 'in', ($control->{source} ? 'source' : $control->{package}), "$field field between '$prev' and '$next'"; } while ($value =~ /([^\s\(]+\s*\([<>]\s*[^<>=]+\))/g) { tag 'obsolete-relation-form-in-source', 'in', ($control->{source} ? 'source' : $control->{package}), "$field: $1"; } } } # Make sure that a stronger dependency field doesn't imply any of the elements # of a weaker dependency field. dpkg-gencontrol will fix this up for us, but # we want to check the source package since dpkg-gencontrol may silently "fix" # something that's a more subtle bug. # # Also check if a package declares a simple dependency on itself, since # similarly dpkg-gencontrol will clean this up for us but it may be a sign of # another problem, and check that the package doesn't hard-code a dependency # on libc. We have to do the latter check here rather than in checks/fields # to distinguish from dependencies created by ${shlibs:Depends}. # # Use this traversal to build a list of package names built from this source # package, which we'll use later to check for dependencies in -dev packages. my @dep_fields = qw(pre-depends depends recommends suggests); my $libcs = Lintian::Relation->new(join(' | ', @LIBCS)); my @package_names; for my $control (@binary_controls) { push (@package_names, $control->{package}); for my $strong (0 .. $#dep_fields) { next unless $control->{$dep_fields[$strong]}; my $relation = Lintian::Relation->new($control->{$dep_fields[$strong]}); tag 'package-depends-on-itself', $control->{package}, $dep_fields[$strong] if $relation->implies($control->{package}); tag 'package-depends-on-hardcoded-libc', $control->{package}, $dep_fields[$strong] if ($relation->implies($libcs) and $pkg !~ /^e?glibc$/); for my $weak (($strong + 1) .. $#dep_fields) { next unless $control->{$dep_fields[$weak]}; for my $dependency (split /\s*,\s*/, $control->{$dep_fields[$weak]}) { next unless $dependency; tag 'stronger-dependency-implies-weaker', $control->{package}, "$dep_fields[$strong] -> $dep_fields[$weak]", $dependency if $relation->implies($dependency); } } } } # Check that every package is in the same archive area, except that # sources in main can deliver both main and contrib packages. The source # package may or may not have a section specified; if it doesn't, derive the # expected archive area from the first binary package by leaving $area # undefined until parsing the first binary section. Missing sections will be # caught by other checks. # # Check any package that looks like a library -dev package for a dependency on # a shared library package built from the same source. If found, such a # dependency should have a tight version dependency on that package. # # Also accumulate short and long descriptions for each package so that we can # check for duplication, but skip udeb packages. Ideally, we should check the # udeb package descriptions separately for duplication, but udeb packages # should be able to duplicate the descriptions of non-udeb packages and the # package description for udebs is much less important or significant to the # user. my $area; if ($header->{'section'}) { if ($header->{'section'} =~ m%^([^/]+)/%) { $area = $1; } else { $area = ''; } } else { tag 'no-section-field-for-source'; } my @descriptions; for my $binary_control (@binary_controls) { my $package = $binary_control->{'package'}; # Accumulate the description. my $desc = $binary_control->{'description'}; if ($desc and (not $binary_control->{'xc-package-type'} or $binary_control->{'xc-package-type'} ne 'udeb')) { push(@descriptions, [ $package, split("\n", $desc, 2) ]); } # If this looks like a -dev package, check its dependencies. if ($package =~ /-dev$/ and $binary_control->{'depends'}) { check_dev_depends ($info, $package, $binary_control->{depends}, @package_names); } # Check mismatches in archive area. next unless $binary_control->{'section'}; if (!defined ($area)) { if ($binary_control->{'section'} =~ m%^([^/]+)/%) { $area = ($1 eq 'contrib') ? '' : $1; } else { $area = ''; } next; } tag 'section-area-mismatch', 'Package ' . $package if ($area && $binary_control->{'section'} !~ m%^$area/%); tag 'section-area-mismatch', 'Package ' . $package if (!$area && $binary_control->{'section'} =~ m%^([^/]+)/% && $1 ne 'contrib'); } # Check for duplicate descriptions. my (%seen_short, %seen_long); for my $i (0 .. $#descriptions) { my (@short, @long); for my $j (($i + 1) .. $#descriptions) { if ($descriptions[$i][1] eq $descriptions[$j][1]) { my $package = $descriptions[$j][0]; push(@short, $package) unless $seen_short{$package}; } next unless ($descriptions[$i][2] and $descriptions[$j][2]); if ($descriptions[$i][2] eq $descriptions[$j][2]) { my $package = $descriptions[$j][0]; push(@long, $package) unless $seen_long{$package}; } } if (@short) { tag 'duplicate-short-description', $descriptions[$i][0], @short; for (@short) { $seen_short{$_} = 1 } } if (@long) { tag 'duplicate-long-description', $descriptions[$i][0], @long; for (@long) { $seen_long{$_} = 1 } } } } # Check the dependencies of a -dev package. Any dependency on one of the # packages in @package_names that looks like the underlying library needs to # have a version restriction that's at least as strict as the same upstream # version. sub check_dev_depends { my ($info, $package, $depends, @packages) = @_; $depends =~ s/^\s+//; $depends =~ s/\s+$//; for my $target (@packages) { next unless ($target =~ /^lib[\w.+-]+\d/ and $target !~ /-(?:dev|docs?|common)$/); my @depends = grep { /(?:^|[\s|])\Q$target\E(?:[\s|\(]|\z)/ } split (/\s*,\s*/, $depends); # If there are any alternatives here, something special is # going on. Assume that the maintainer knows what they're # doing. Otherwise, separate out just the versions. next if grep { /\|/ } @depends; my @versions = sort map { if (/^[\w.+-]+(?:\s*\(([^\)]+)\))/) { $1; } else { ''; } } @depends; # If there's only one mention of this package, the dependency # should be tight. Otherwise, there should be both >>/>= and # <binary_field ($target, 'architecture')//'') eq 'all' && $versions[0] =~ /^\s*=\s*\$\{source:Version\}/; tag 'weak-library-dev-dependency', "$package on $depends[0]"; } } elsif (@depends == 2) { unless ($versions[0] =~ /^\s*<[=<]\s*\$\{(?:(?:binary|source):(?:Upstream-)?Version|Source-Version)\}/ && $versions[1] =~ /^\s*>[=>]\s*\$\{(?:(?:binary|source):(?:Upstream-)?Version|Source-Version)\}/) { tag 'weak-library-dev-dependency', "$package on $depends[0], $depends[1]"; } } } } 1; # Local Variables: # indent-tabs-mode: nil # cperl-indent-level: 4 # End: # vim: syntax=perl sw=4 sts=4 sr et