# fields -- lintian check script (rewrite) -*- perl -*- # # Copyright (C) 2004 Marc Brockschmidt # # Parts of the code were taken from the old check script, which # was Copyright (C) 1998 Richard Braakman (also licensed under the # GPL 2 or higher) # # 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 Maemian::fields; use strict; use lib "$ENV{'MAEMIAN_ROOT'}/checks/"; use common_data; use Tags; use Util; use Maemian::Data (); use Maemian::Check qw(check_maintainer); use Maemian::Relation (); use Maemian::Relation::Version qw(versions_compare); our $KNOWN_ARCHS = Maemian::Data->new ('fields/architectures'); our %known_archive_parts = map { $_ => 1 } ('non-free', 'contrib'); our %known_sections = map { $_ => 1 } ('admin', 'comm', 'cli-mono', 'database', 'debug', 'devel', 'doc', 'editors', 'electronics', 'embedded', 'fonts', 'games', 'gnome', 'gnu-r', 'gnustep', 'graphics', 'hamradio', 'haskell', 'httpd', 'interpreters', 'java', 'kde', 'libdevel', 'libs', 'lisp', 'localization', 'kernel', 'mail', 'math', 'misc', 'net', 'news', 'ocaml', 'oldlibs', 'otherosfs', 'perl', 'php', 'python', 'ruby', 'science', 'shells', 'sound', 'tex', 'text', 'utils', 'vcs', 'video', 'web', 'x11', 'xfce', 'zope' ); our %known_prios = map { $_ => 1 } ('required', 'important', 'standard', 'optional', 'extra'); # The Ubuntu original-maintainer field is handled separately. our %known_binary_fields = map { $_ => 1 } ('package', 'version', 'architecture', 'depends', 'pre-depends', 'recommends', 'suggests', 'enhances', 'conflicts', 'provides', 'replaces', 'breaks', 'essential', 'maintainer', 'section', 'priority', 'source', 'description', 'installed-size', 'python-version', 'homepage', 'bugs', 'origin'); # The Ubuntu original-maintainer field is handled separately. our %known_udeb_fields = map { $_ => 1 } ('package', 'version', 'architecture', 'subarchitecture', 'depends', 'recommends', 'enhances', 'provides', 'replaces', 'breaks', 'replaces', 'maintainer', 'section', 'priority', 'source', 'description', 'installed-size', 'kernel-version', 'installer-menu-item', 'bugs', 'origin'); our %known_obsolete_fields = map { $_ => 1 } ('revision', 'package-revision', 'package_revision', 'recommended', 'optional', 'class'); our %known_build_essential = map { $_ => 1 } ('libc6-dev', 'libc-dev', 'gcc', 'g++', 'make', 'dpkg-dev'); # Still in the archive but shouldn't be the primary Emacs dependency. our %known_obsolete_emacs = map { $_ => 1 } ('emacs21'); our %known_libstdcs = map { $_ => 1 } ('libstdc++2.9-glibc2.1', 'libstdc++2.10', 'libstdc++2.10-glibc2.2', 'libstdc++3', 'libstdc++3.0', 'libstdc++4', 'libstdc++5', 'libstdc++6', 'lib64stdc++6', ); our %known_tcls = map { $_ => 1 } ( 'tcl74', 'tcl8.0', 'tcl8.2', 'tcl8.3', 'tcl8.4', 'tcl8.5', ); our %known_tclxs = map { $_ => 1 } ( 'tclx76', 'tclx8.0.4', 'tclx8.2', 'tclx8.3', 'tclx8.4', ); our %known_tks = map { $_ => 1 } ( 'tk40', 'tk8.0', 'tk8.2', 'tk8.3', 'tk8.4', 'tk8.5', ); our %known_tkxs = map { $_ => 1 } ( 'tkx8.2', 'tkx8.3', ); our %known_libpngs = map { $_ => 1 } ( 'libpng12-0', 'libpng2', 'libpng3', ); our %known_x_metapackages = map { $_ => 1 } ( 'x-window-system', 'x-window-system-dev', 'x-window-system-core', 'xorg', 'xorg-dev', ); # The allowed Python dependencies currently. This is the list of alternatives # that, either directly or through transitive dependencies that can be relied # upon, ensure /usr/bin/python will exist for the use of dh_python. our $PYTHON_DEPEND = 'python | python-dev | python-all | python-all-dev | ' . join (' | ', map { "python$_ | python$_-dev" } qw(2.4 2.5)); # Certain build tools must be listed in Build-Depends even if there are no # arch-specific packages because they're required in order to run the clean # rule. (See Policy 7.6.) The following is a list of package dependencies; # regular expressions that, if they match anywhere in the debian/rules file, # say that this package is allowed (and required) in Build-Depends; and # optional tags to use for reporting the problem if some information other # than the default is required. our @GLOBAL_CLEAN_DEPENDS = ( [ ant => qr'^include\s*/usr/share/cdbs/1/rules/ant\.mk' ], [ cdbs => qr'^include\s+/usr/share/cdbs/' ], [ dbs => qr'^include\s+/usr/share/dbs/' ], [ 'dh-make-php' => qr'^include\s+/usr/share/cdbs/1/class/pear\.mk' ], [ debhelper => qr'^include\s+/usr/share/cdbs/1/rules/debhelper\.mk' ], [ dpatch => qr'^include\s+/usr/share/cdbs/1/rules/dpatch\.mk' ], [ quilt => qr'^include\s+/usr/share/cdbs/1/rules/patchsys-quilt\.mk' ], [ dpatch => qr'^include\s+/usr/share/dpatch/' ], [ quilt => qr'^include\s+/usr/share/quilt/' ], [ $PYTHON_DEPEND => qr'/usr/share/cdbs/1/class/python-distutils\.mk', 'missing-python-build-dependency' ], ); # These are similar, but the resulting dependency is only allowed, not # required. # # The xsfclean rule is specific to the packages maintained by the X Strike # Force, but there are enough of those to make the rule worthwhile. my @GLOBAL_CLEAN_ALLOWED = ( [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/dpatch\.mk' ], [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/patchsys-quilt\.mk' ], [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/simple-patchsys\.mk' ], [ 'python-central' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pycentral' ], [ 'python-support' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pysupport' ], [ 'python-setuptools' => qr'/usr/share/cdbs/1/class/python-distutils\.mk' ], [ quilt => qr'^clean:\s+xsfclean\b' ], ); # A list of packages; regular expressions that, if they match anywhere in the # debian/rules file, this package must be listed in either Build-Depends or # Build-Depends-Indep as appropriate; and optional tags as above. my @GLOBAL_DEPENDS = ( [ $PYTHON_DEPEND => qr'^\t\s*dh_python\s', 'missing-dh_python-build-dependency' ], [ 'python-central' => qr'^\t\s*dh_pycentral\s' ], [ 'python-support' => qr'^\t\s*dh_pysupport\s' ], [ 'python-central' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pycentral' ], [ 'python-support' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pysupport' ], ); # Similarly, this list of packages, regexes, and optional tags say that if the # regex matches in one of clean, build-arch, binary-arch, or a rule they # depend on, this package is allowed (and required) in Build-Depends. my @RULE_CLEAN_DEPENDS = ( [ ant => qr'^\t\s*ant\s' ], [ debhelper => qr'^\t\s*dh_.+' ], [ dpatch => qr'^\t\s*dpatch\s' ], [ "po-debconf" => qr'^\t\s*debconf-updatepo\s' ], [ $PYTHON_DEPEND => qr'^\t\s*python\s', 'missing-python-build-dependency' ], [ $PYTHON_DEPEND => qr'\ssetup\.py\b', 'missing-python-build-dependency' ], [ quilt => qr'^\t\s*(\S+=\S+\s+)*quilt\s' ], [ yada => qr'^\t\s*yada\s' ], ); # Similar, but the resulting dependency is only allowed, not required. We # permit a versioned dependency on perl-base because that used to be the # standard suggested dependency. No package should be depending on just # perl-base, since it's Priority: required. my @RULE_CLEAN_ALLOWED = ( [ patch => q'^\t\s*(?:perl debian/)?yada\s+unpatch' ], [ 'perl | perl-base (>= 5.6.0-16)' => qr'(^\t|\|\|)\s*(perl|\$\(PERL\))\s' ], [ 'perl-modules (>= 5.10) | libmodule-build-perl' => qr'(^\t|\|\|)\s*(perl|\$\(PERL\))\s+Build\b' ], [ 'python-setuptools' => qr'\ssetup\.py\b' ], ); # A simple list of regular expressions which, if they match anywhere in # debian/rules, indicate the requirements for debian/rules clean are complex # enough that we can't know what packages are permitted in Build-Depends and # should bypass the build-depends-without-arch-dep check completely. my @GLOBAL_CLEAN_BYPASS = ( qr'^include\s*/usr/share/cdbs/1/class/ant\.mk', qr'^\s+dh\s+' ); # Mapping of package names to section names my @NAME_SECTION_MAPPINGS = ( [ qr/-docs?$/ => 'doc' ], [ qr/-dbg$/ => 'debug' ], [ qr/^python-/ => 'python' ], [ qr/^r-cran-/ => 'gnu-r' ], [ qr/^lib.*-perl$/ => 'perl' ], [ qr/^lib.*-cil$/ => 'cli-mono' ], [ qr/^lib.*-java$/ => 'java' ], [ qr/^(?:lib)php-/ => 'php' ], [ qr/^lib(?:hugs|ghc6)-/ => 'haskell' ], [ qr/^lib.*-ruby(?:1\.\d)?$/ => 'ruby' ], [ qr/^lib.*-ocaml-dev$/ => 'ocaml' ], [ qr/^lib.*-dev$/ => 'libdevel' ], ); # Valid URI formats for the Vcs-* fields # currently only checks the protocol, not the actual format of the URI my %VCS_RECOMMENDED_URIS = ( browser => qr;^https?://;, arch => qr;^https?://;, bzr => qr;^(lp:~|(?:nosmart\+)?https?://);, cvs => qr;^:pserver:;, darcs => qr;^https?://;, hg => qr;^https?://;, git => qr;^(?:git|https?|rsync)://;, svn => qr;^(?:svn|(?:svn\+)?https?)://;, mtn => qr;^[\w.-]+\s+\S+;, # that's a hostname followed by a module name ); my %VCS_VALID_URIS = ( arch => qr;^https?://;, bzr => qr;^(?:sftp|(?:bzr\+)?ssh)://;, cvs => qr;^(?:-d\s*)?:(?:ext|pserver):;, git => qr;^(?:git\+)?ssh://;, svn => qr;^(?:svn\+)?ssh://;, ); our $PERL_CORE_PROVIDES = Maemian::Data->new('fields/perl-provides', '\s+'); our $OBSOLETE_PACKAGES = Maemian::Data->new('fields/obsolete-packages'); our $VIRTUAL_PACKAGES = Maemian::Data->new('fields/virtual-packages'); sub run { my $pkg = shift; my $type = shift; my $info = shift; my $version; my $arch_indep; unless (-d "fields") { fail("directory in lintian laboratory for $type package $pkg missing: fields"); } #---- Format if ($type eq 'source') { my $format = $info->field('format'); if (defined($format) and $format !~ /^\s*1\.0\s*\z/) { tag 'unsupported-source-format', $format; } } #---- Package if ($type eq "binary"){ if (not defined $info->field('package')) { tag "no-package-name", ""; } else { my $name = $info->field('package'); unfold("package", \$name); tag "bad-package-name", "" unless $name =~ /^[A-Z0-9][-+\.A-Z0-9]+$/i; tag "package-not-lowercase", "" if ($name =~ /[A-Z]/) } } #---- Version if (not defined $info->field('version')) { tag "no-version-field", ""; } else { $version = $info->field('version'); unfold("version", \$version); if (@_ = _valid_version($version)) { my ($epoch, $upstream, $debian) = @_; if ($upstream !~ /^\d/i) { tag "upstream-version-not-numeric", "$version"; } if (defined $debian) { tag "debian-revision-should-not-be-zero", "$version" if $debian eq '-0'; my $ubuntu; $debian =~ /^-([^.]+)(?:\.[^.]+)?(?:\.[^.]+)?(\..*)?$/; my $extra = $2; if (defined $extra) { $debian =~ /^-([^.]+ubuntu[^.]+)(?:\.\d+){1,3}(\..*)?$/; $ubuntu = 1; $extra = $2; } if (not defined $1 or defined $extra) { tag "debian-revision-not-well-formed", "$version"; } if ($debian =~ /^-[^.-]+\.[^.-]+\./ and not $ubuntu) { tag "binary-nmu-uses-old-version-style", "$version" if $type eq 'binary'; tag "binary-nmu-debian-revision-in-source", "$version" if $type eq 'source'; } } if ($version =~ /\+b\d+$/ && $type eq "source") { tag "binary-nmu-debian-revision-in-source", "$version"; } # Checks for the dfsg convention for repackaged upstream # source. Only check these against the source package to not # repeat ourselves too much. if ($type eq 'source') { if ($version =~ /dfsg/ and $info->native) { tag 'dfsg-version-in-native-package', $version; } elsif ($version =~ /\.dfsg/) { tag 'dfsg-version-with-period', $version; } elsif ($version =~ /dsfg/) { tag 'dfsg-version-misspelled', $version; } } my $name = $info->field('package'); if ($name && $PERL_CORE_PROVIDES->known($name) && perl_core_has_version($name, '>=', $upstream)) { my $core_version = $PERL_CORE_PROVIDES->value($name); tag "package-superseded-by-perl", "with $core_version" } } else { tag "bad-version-number", "$version"; } } #---- Architecture if (not defined $info->field('architecture')) { tag "no-architecture-field", ""; } else { my $archs = $info->field('architecture'); unfold("architecture", \$archs); my @archs = split / /, $archs; if (@archs > 1 && grep { $_ eq "any" || ($type ne "source" && $_ eq "all") } @archs) { tag "magic-arch-in-arch-list", ""; } for my $arch (@archs) { tag "unknown-architecture", "$arch" unless $KNOWN_ARCHS->known($arch); } if ($type eq "binary") { tag "too-many-architectures", "" if (@archs > 1); tag "arch-any-in-binary-pkg", "" if (grep { $_ eq "any" } @archs); tag "aspell-package-not-arch-all", "" if ($pkg =~ /^aspell-[a-z]{2}(-.*)?$/ && (@archs > 1 || $archs[0] ne 'all')); } # Used for later tests. $arch_indep = 1 if (@archs == 1 && $archs[0] eq 'all'); } #---- Subarchitecture (udeb) if (defined $info->field('subarchitecture')) { my $subarch = $info->field('subarchitecture'); unfold("subarchitecture", \$subarch); } #---- Maintainer #---- Uploaders for my $f (qw(maintainer uploaders)) { if (not defined $info->field($f)) { tag "no-maintainer-field", "" if $f eq "maintainer"; } else { my $maintainer = $info->field($f); # Note, not expected to hit on uploaders anymore, as dpkg now strips # newlines for the .dsc, and the newlines don't hurt in debian/control unfold($f, \$maintainer); if ($f eq "uploaders") { my @uploaders = split /\s*,\s*/, $maintainer; my %duplicate_uploaders; for my $uploader (@uploaders) { check_maintainer($uploader, "uploader"); if ( ((grep { $_ eq $uploader } @uploaders) > 1) and ($duplicate_uploaders{$uploader}++ == 0)) { tag 'duplicate-uploader', $uploader; } } } else { check_maintainer($maintainer, $f); if ($type eq 'source' && $maintainer =~ /\@lists(\.alioth)?\.debian\.org\b/ && ! defined $info->field('uploaders')) { tag 'no-human-maintainers'; } } } } if (defined $info->field('uploaders') && defined $info->field('maintainer')) { my $maint = $info->field('maintainer'); tag 'maintainer-also-in-uploaders' if $info->field('uploaders') =~ m/\Q$maint/; } #---- Source if (not defined $info->field('source')) { tag "no-source-field" if $type eq "source"; } else { my $source = $info->field('source'); unfold("source", \$source); if ($type eq 'source') { if ($source ne $pkg) { tag "source-field-does-not-match-pkg-name", "$source != $pkg"; } } else { if ($source !~ /[A-Z0-9][-+\.A-Z0-9]+ #Package name \s* (?:\((?:\d+:)?(?:[-\.+:A-Z0-9~]+?)(?:-[\.+A-Z0-9~]+)?\))?\s*$/ix) { #Version tag "source-field-malformed", "$source"; } } } #---- Essential if (defined $info->field('essential')) { my $essential = $info->field('essential'); unfold("essential", \$essential); tag "essential-in-source-package", "" if ($type eq "source"); tag "essential-no-not-needed", "" if ($essential eq "no"); tag "unknown-essential-value", "" if ($essential ne "no" and $essential ne "yes"); tag "new-essential-package", "" if ($essential eq "yes" and ! $known_essential{$pkg}); } #---- Section if (not defined $info->field('section')) { tag 'no-section-field' if ($type eq 'binary'); } else { my $section = $info->field('section'); unfold("section", \$section); if ($type eq 'udeb') { unless ($section eq 'debian-installer') { tag "wrong-section-for-udeb", "$section"; } } else { my @parts = split /\//, $section, 2; if (scalar @parts > 1) { tag "unknown-section", "$section" unless $known_archive_parts{$parts[0]}; tag "unknown-section", "$section" unless $known_sections{$parts[1]}; } elsif ($parts[0] eq 'unknown') { tag "section-is-dh_make-template"; } else { tag "unknown-section", "$section" unless $known_sections{$parts[0]}; } # Check package name <-> section. foreach my $map (@NAME_SECTION_MAPPINGS) { if ($pkg =~ $map->[0]) { tag "wrong-section-according-to-package-name", "$pkg => $map->[1]" unless $parts[-1] eq $map->[1]; last; } } } } #---- Priority if (not defined $info->field('priority')) { tag "no-priority-field", "" if $type eq "binary"; } else { my $priority = $info->field('priority'); unfold("priority", \$priority); tag "unknown-priority", "$priority" if (! $known_prios{$priority}); if ($pkg =~ /-dbg$/) { tag "debug-package-should-be-priority-extra", $pkg unless $priority eq 'extra'; } } #---- Standards-Version # handled in checks/standards-version #---- Description # handled in checks/description #--- Homepage if (defined $info->field('homepage')) { my $homepage = $info->field('homepage'); unfold("homepage", \$homepage); if ($homepage =~ /^<(?:UR[LI]:)?.*>$/i) { tag "superfluous-clutter-in-homepage", $homepage; } require URI; my $uri = URI->new($homepage); unless ($uri->scheme) { # not an absolute URI tag "bad-homepage", $homepage; } if ($homepage =~ m,/search\.cpan\.org/.*-[0-9._]+/*$,) { tag 'homepage-for-cpan-package-contains-version', $homepage; } } elsif ($type eq "binary" and not $info->native) { tag "no-homepage-field"; } #---- Installer-Menu-Item (udeb) if (defined $info->field('installer-menu-item')) { my $menu_item = $info->field('installer-menu-item'); unfold('installer-menu-item', \$menu_item); $menu_item =~ /^\d+$/ or tag "bad-menu-item", "$menu_item"; } #---- Package relations (binary package) # Check whether the package looks like a meta-package, used for later # dependency checks. We consider a package to possibly be a meta-package if # it is a binary package, arch: all, with no files outside of /usr/share/doc. my $metapackage = 0; if ($type eq 'binary' && $arch_indep) { $metapackage = 1; foreach my $file (keys %{$info->index}) { $metapackage = 0 unless ($info->index->{$file}->{type} =~ /^d/ || $file =~ m%^usr/share/doc/%); } } if (($type eq "binary") || ($type eq 'udeb')) { my (%deps, %fields, %parsed); for my $field (qw(depends pre-depends recommends suggests conflicts provides enhances replaces breaks)) { if (defined $info->field($field)) { #Get data and clean it my $data = $info->field($field);; unfold($field, \$data); $fields{$field} = $data; my (@seen_libstdcs, @seen_tcls, @seen_tclxs, @seen_tks, @seen_tkxs, @seen_libpngs); my $is_dep_field = sub { grep { $_ eq $_[0] } qw(depends pre-depends recommends suggests) }; tag "alternates-not-allowed", "$field" if ($data =~ /\|/ && ! &$is_dep_field($field)); for my $dep (split /\s*,\s*/, $data) { my (@alternatives, @seen_obsolete_packages); push @alternatives, [_split_dep($_), $_] for (split /\s*\|\s*/, $dep); if (&$is_dep_field($field)) { push @seen_libstdcs, $alternatives[0]->[0] if defined $known_libstdcs{$alternatives[0]->[0]}; push @seen_tcls, $alternatives[0]->[0] if defined $known_tcls{$alternatives[0]->[0]}; push @seen_tclxs, $alternatives[0]->[0] if defined $known_tclxs{$alternatives[0]->[0]}; push @seen_tks, $alternatives[0]->[0] if defined $known_tks{$alternatives[0]->[0]}; push @seen_tkxs, $alternatives[0]->[0] if defined $known_tkxs{$alternatives[0]->[0]}; push @seen_libpngs, $alternatives[0]->[0] if defined $known_libpngs{$alternatives[0]->[0]}; } # Only for (Pre-)?Depends. tag "virtual-package-depends-without-real-package-depends", "$field: $alternatives[0]->[0]" if ($VIRTUAL_PACKAGES->known($alternatives[0]->[0]) && ($field eq "depends" || $field eq "pre-depends")); # Check defaults for transitions. Here, we only care that the first alternative is current. tag "depends-on-old-emacs", "$field: $alternatives[0]->[0]" if (&$is_dep_field($field) && $known_obsolete_emacs{$alternatives[0]->[0]}); for my $part_d (@alternatives) { my ($d_pkg, $d_version, $d_arch, $rest, $part_d_orig) = @$part_d; tag "versioned-provides", "$part_d_orig" if ($field eq "provides" && $d_version->[0]); tag "breaks-without-version", "$part_d_orig" if ($field eq "breaks" && !$d_version->[0]); tag "obsolete-relation-form", "$field: $part_d_orig" if ($d_version && grep { $d_version->[0] eq $_ } ("<", ">")); tag "bad-version-in-relation", "$field: $part_d_orig" if ($d_version->[0] && ! defined((_valid_version($d_version->[1]))[1])); tag "package-relation-with-self", "$field: $part_d_orig" if ($pkg eq $d_pkg) && ($field ne 'conflicts'); tag "bad-relation", "$field: $part_d_orig" if $rest; push @seen_obsolete_packages, $part_d_orig if ($OBSOLETE_PACKAGES->known($d_pkg) && &$is_dep_field($field)); tag "depends-on-x-metapackage", "$field: $part_d_orig" if ($known_x_metapackages{$d_pkg} && ! $metapackage && &$is_dep_field($field)); tag "depends-on-essential-package-without-using-version", "$field: $part_d_orig" if ($known_essential{$d_pkg} && ! $d_version->[0] && &$is_dep_field($field)); tag "package-depends-on-an-x-font-package", "$field: $part_d_orig" if ($field =~ /^(pre-)?depends$/ && $d_pkg =~ /^xfont.*/ && $d_pkg ne 'xfonts-utils' && $d_pkg ne 'xfongs-encodings'); tag "needlessly-depends-on-awk", "$field" if ($d_pkg eq "awk" && ! $d_version->[0] && &$is_dep_field($field)); tag "depends-on-libdb1-compat", "$field" if ($d_pkg eq "libdb1-compat" && $pkg !~ /^libc(6|6.1|0.3)/ && $field =~ /^(pre-)depends$/); tag "depends-on-python-minimal", "$field", if ($d_pkg =~ /^python[\d.]*-minimal$/ && &$is_dep_field($field) && $pkg !~ /^python[\d.]*-minimal$/); tag "doc-package-depends-on-main-package", "$field" if ("$d_pkg-doc" eq $pkg && $field =~ /^(pre-)depends$/); tag "old-versioned-python-dependency", "$field: $part_d_orig" if ($d_pkg eq 'python' && $d_version->[0] eq '<<' && &$is_dep_field($field) && $arch_indep && $pkg =~ /^python-/ && ! defined $info->field('python-version') && ! $info->relation('depends')->implies('python-support')); # only trigger this for the the preferred alternative tag "versioned-dependency-satisfied-by-perl", "$field: $part_d_orig" if $alternatives[0][-1] eq $part_d_orig && &$is_dep_field($field) && perl_core_has_version($d_pkg, $d_version->[0], $d_version->[1]); tag "depends-exclusively-on-makedev", "$field", if ($field eq 'depends' && $d_pkg eq 'makedev' && @alternatives == 1); } for my $pkg (@seen_obsolete_packages) { if ($pkg eq $alternatives[0]->[0] or scalar @seen_obsolete_packages == scalar @alternatives) { tag "depends-on-obsolete-package", "$field: $pkg"; } else { tag "ored-depends-on-obsolete-package", "$field: $pkg"; } } } tag "package-depends-on-multiple-libstdc-versions", @seen_libstdcs if (scalar @seen_libstdcs > 1); tag "package-depends-on-multiple-tcl-versions", @seen_tcls if (scalar @seen_tcls > 1); tag "package-depends-on-multiple-tclx-versions", @seen_tclxs if (scalar @seen_tclxs > 1); tag "package-depends-on-multiple-tk-versions", @seen_tks if (scalar @seen_tks > 1); tag "package-depends-on-multiple-tkx-versions", @seen_tkxs if (scalar @seen_tkxs > 1); tag "package-depends-on-multiple-libpng-versions", @seen_libpngs if (scalar @seen_libpngs > 1); } } # If Conflicts or Breaks is set, make sure it's not inconsistent with # the other dependency fields. for my $conflict (qw/conflicts breaks/) { next unless $fields{$conflict}; for my $field (qw(depends pre-depends recommends suggests)) { next unless $info->field($field); my $relation = $info->relation($field); for my $package (split /\s*,\s*/, $fields{$conflict}) { tag "conflicts-with-dependency", $field, $package if $relation->implies($package); } } } } #---- Package relations (source package) if ($type eq "source") { my $binpkgs = $info->binaries; #Get number of arch-indep packages: my $arch_indep_packages = 0; my $arch_dep_packages = 0; foreach my $binpkg (keys %$binpkgs) { my $arch = $info->binary_field($binpkg, 'architecture'); if ($arch eq 'all') { $arch_indep_packages++; } else { $arch_dep_packages++; } } # Search through rules and determine which dependencies are required. # The keys in %needed and %needed_clean are the dependencies; the # values are the tags to use or the empty string to use the default # tag. my (%needed, %needed_clean, %allowed_clean, $bypass_needed_clean); open (RULES, '<', "debfiles/rules") or fail("cannot read debfiles/rules: $!"); my $target = "none"; my @rules = qw(clean binary-arch build-arch); my $maybe_skipping; while () { if (/^ifn?(eq|def)\s/) { $maybe_skipping++; } elsif (/^endif\s/) { $maybe_skipping--; } for my $rule (@GLOBAL_CLEAN_DEPENDS) { if ($_ =~ /$rule->[1]/) { if ($maybe_skipping) { $allowed_clean{$rule->[0]} = 1; } else { $needed_clean{$rule->[0]} = $rule->[2] || $needed_clean{$rule->[0]} || ''; } } } for my $rule (@GLOBAL_CLEAN_ALLOWED) { if ($_ =~ /$rule->[1]/) { $allowed_clean{$rule->[0]} = 1; } } for my $rule (@GLOBAL_CLEAN_BYPASS) { if ($_ =~ /$rule/) { $bypass_needed_clean = 1; } } for my $rule (@GLOBAL_DEPENDS) { if ($_ =~ /$rule->[1]/ && !$maybe_skipping) { $needed{$rule->[0]} = $rule->[2] || $needed{$rule->[0]} || ''; } } if (/^(\S+?):+(.*)/) { $target = $1; if (grep ($_ eq $target, @rules)) { push (@rules, split (' ', $2)); } } if (grep ($_ eq $target, @rules)) { for my $rule (@RULE_CLEAN_DEPENDS) { if ($_ =~ /$rule->[1]/) { if ($maybe_skipping) { $allowed_clean{$rule->[0]} = 1; } else { $needed_clean{$rule->[0]} = $rule->[2] || $needed_clean{$rule->[0]} || ''; } } } for my $rule (@RULE_CLEAN_ALLOWED) { if ($_ =~ /$rule->[1]/) { $allowed_clean{$rule->[0]} = 1; } } } } close RULES; tag "build-depends-indep-without-arch-indep", "" if (defined $info->field('build-depends-indep') && $arch_indep_packages == 0); my $is_dep_field = sub { grep { $_ eq $_[0] } qw(build-depends build-depends-indep) }; my %depend; for my $field (qw(build-depends build-depends-indep build-conflicts build-conflicts-indep)) { if (defined $info->field($field)) { #Get data and clean it my $data = $info->field($field);; unfold($field, \$data); $depend{$field} = $data; for my $dep (split /\s*,\s*/, $data) { my (@alternatives, @seen_obsolete_packages); push @alternatives, [_split_dep($_), $_] for (split /\s*\|\s*/, $dep); tag "virtual-package-depends-without-real-package-depends", "$field: $alternatives[0]->[0]" if ($VIRTUAL_PACKAGES->known($alternatives[0]->[0]) && &$is_dep_field($field)); for my $part_d (@alternatives) { my ($d_pkg, $d_version, $d_arch, $rest, $part_d_orig) = @$part_d; for my $arch (@{$d_arch->[0]}) { if (!$KNOWN_ARCHS->known($arch)) { tag "invalid-arch-string-in-source-relation", "$arch [$field: $part_d_orig]" } } tag "build-depends-on-build-essential", $field if ($d_pkg eq "build-essential"); tag "depends-on-build-essential-package-without-using-version", "$d_pkg [$field: $part_d_orig]" if ($known_build_essential{$d_pkg} && ! $d_version->[1]); tag "build-depends-on-essential-package-without-using-version", "$field: $part_d_orig" if ($d_pkg ne "coreutils" && $known_essential{$d_pkg} && ! $d_version->[0]); push @seen_obsolete_packages, $part_d_orig if ($OBSOLETE_PACKAGES->known($d_pkg) && &$is_dep_field($field)); tag "build-depends-on-x-metapackage", "$field: $part_d_orig" if ($known_x_metapackages{$d_pkg} && &$is_dep_field($field)); tag "build-depends-on-1-revision", "$field: $part_d_orig" if ($d_version->[0] eq '>=' && $d_version->[1] =~ /-1$/ && &$is_dep_field($field)); tag "bad-relation", "$field: $part_d_orig" if $rest; # only trigger this for the the preferred alternative tag "versioned-dependency-satisfied-by-perl", "$field: $part_d_orig" if $alternatives[0][-1] eq $part_d_orig && &$is_dep_field($field) && perl_core_has_version($d_pkg, $d_version->[0], $d_version->[1]); } for my $pkg (@seen_obsolete_packages) { if ($pkg eq $alternatives[0]->[0] or scalar @seen_obsolete_packages == scalar @alternatives) { tag "build-depends-on-obsolete-package", "$field: $pkg"; } else { tag "ored-build-depends-on-obsolete-package", "$field: $pkg"; } } } } } # Check for duplicates. my $build_all = $info->relation('build-depends-all'); my @dups = $build_all->duplicates; for my $dup (@dups) { tag "package-has-a-duplicate-build-relation", join (', ', @$dup); } # Make sure build dependencies and conflicts are consistent. my %parsed; for ($depend{'build-conflicts'}, $depend{'build-conflicts-indep'}) { next unless $_; for my $conflict (split /\s*,\s*/, $_) { if ($build_all->implies($conflict)) { tag "build-conflicts-with-build-dependency", $conflict; } } } # Make sure that all the required build dependencies are there. Don't # issue missing-build-dependency errors for debhelper, since there's # another test that does that and it would just be a duplicate. my $build_regular = $info->relation('build-depends'); my $build_indep = $info->relation('build-depends-indep'); for my $package (keys %needed_clean) { my $tag = $needed_clean{$package} || 'missing-build-dependency'; unless ($build_regular->implies($package)) { if ($build_indep->implies($package)) { tag "clean-should-be-satisfied-by-build-depends", $package; } else { if ($tag eq 'missing-build-dependency') { tag $tag, $package if $package ne 'debhelper'; } else { tag $tag; } } } } my $noarch = $info->relation_noarch('build-depends-all'); for my $package (keys %needed) { my $tag = $needed{$package} || 'missing-build-dependency'; # dh_python deactivates itself if the new Python build policy # is enabled. if ($tag eq 'missing-dh_python-build-dependency') { next if -f 'debfiles/pycomat'; next if defined $info->field('python-version'); } unless ($noarch->implies($package)) { if ($tag eq 'missing-build-dependency') { tag $tag, $package; } else { tag $tag; } } } # This check is a bit tricky. We want to allow in Build-Depends a # dependency with any version, since reporting this tag over version # mismatches would be confusing and quite likely wrong. The approach # taken is to strip the version information off all dependencies # allowed in Build-Depends, strip the version information off of the # dependencies in Build-Depends, and then allow any dependency in # Build-Depends that's implied by the dependencies we require or allow # there. # # We also have to map | to , when building the list of allowed # packages so that the implications will work properly. # # This is confusing. There should be a better way to do this. if (defined $info->field('build-depends') && $arch_dep_packages == 0 && !$bypass_needed_clean) { my $build_depends = $info->field('build-depends'); my @packages = split /\s*,\s*/, $build_depends; my @allowed = map { s/\([^\)]+\)//g; s/\|/,/g; $_ } keys (%needed_clean), keys (%allowed_clean); my $dep = Maemian::Relation->new_noarch(join(',', @allowed)); foreach my $pkg (@packages) { my $name = $pkg; $name =~ s/[\[\(][^\)\]]+[\)\]]//g; $name =~ s/\s+$//; $name =~ s/\s+/ /g; unless ($dep->implies($name)) { tag "build-depends-without-arch-dep", $name; } } } my (@arch_dep_pkgs, @dbg_pkgs); foreach my $binpkg (keys %$binpkgs) { if ($binpkg =~ m/-dbg$/) { push @dbg_pkgs, $binpkg; } elsif ($info->binary_field($binpkg, 'architecture') ne 'all') { push @arch_dep_pkgs, $binpkg; } } foreach (@dbg_pkgs) { my $deps; $deps = $info->binary_field($_, 'pre-depends') . ', '; $deps .= $info->binary_field($_, 'depends'); tag 'dbg-package-missing-depends', $_ unless (grep {my $quoted_name = qr<\Q$_>; $deps =~ m/(\s|,|^)$quoted_name(\s|,|$)/} @arch_dep_pkgs); } } #----- Origin if (defined $info->field('origin')) { my $origin = $info->field('origin'); unfold('origin', \$origin); tag "redundant-origin-field", "" if lc($origin) eq 'debian'; } #----- Bugs if (defined $info->field('bugs')) { my $bugs = $info->field('bugs'); unfold('bugs', \$bugs); tag "redundant-bugs-field" if $bugs =~ m,^debbugs://bugs.debian.org/?$,i; } #----- Python-Version if (defined $info->field('python-version')) { my $pyversion = $info->field('python-version'); unfold('python-version', \$pyversion); my @valid = ([ '\d+\.\d+', '\d+\.\d+' ], [ '\d+\.\d+' ], [ '\>=\s*\d+\.\d+', '\<\<\s*\d+\.\d+' ], [ '\>=\s*\d+\.\d+' ], [ 'current', '\>=\s*\d+\.\d+' ], [ 'current' ], [ 'all' ]); my @pyversion = split(/\s*,\s*/, $pyversion); if (@pyversion > 2) { if (grep { !/^\d+\.\d+$/ } @pyversion) { tag "malformed-python-version", "$pyversion"; } } else { my $okay = 0; for my $rule (@valid) { if ($pyversion[0] =~ /^$rule->[0]$/ && (($pyversion[1] && $rule->[1] && $pyversion[1] =~ /^$rule->[1]$/) || (! $pyversion[1] && ! $rule->[1]))) { $okay = 1; last; } } tag "malformed-python-version", "$pyversion" unless $okay; } } #----- Dm-Upload-Allowed if (defined $info->field('dm-upload-allowed')) { my $dmupload = $info->field('dm-upload-allowed'); unfold('dm-upload-allowed', \$dmupload); unless ($dmupload eq 'yes') { tag "malformed-dm-upload-allowed", "$dmupload"; } } #----- Vcs-* while (my ($vcs, $regex) = each %VCS_RECOMMENDED_URIS) { if (defined $info->field("vcs-$vcs")) { my $uri = $info->field("vcs-$vcs"); if ($uri !~ $regex) { if ($VCS_VALID_URIS{$vcs} and $uri =~ $VCS_VALID_URIS{$vcs}) { tag "vcs-field-uses-not-recommended-uri-format", "vcs-$vcs", $uri; } else { tag "vcs-field-uses-unknown-uri-format", "vcs-$vcs", $uri; } } } } #----- Field checks (without checking the value) for my $field (glob("fields/*")) { $field =~ s!^fields/!!; next if ($field eq 'original-maintainer') and $version =~ /ubuntu/; tag "obsolete-field", "$field" if $known_obsolete_fields{$field}; tag "unknown-field-in-dsc", "$field" if ($type eq "source" && ! $known_source_fields{$field} && ! $known_obsolete_fields{$field}); tag "unknown-field-in-control", "$field" if ($type eq "binary" && ! $known_binary_fields{$field} && ! $known_obsolete_fields{$field}); tag "unknown-field-in-control", "$field" if ($type eq "udeb" && ! $known_udeb_fields{$field} && ! $known_obsolete_fields{$field}); } } # splits "foo (>= 1.2.3) [!i386 ia64]" into # ( "foo", [ ">=", "1.2.3" ], [ [ "i386", "ia64" ], 1 ], "" ) # ^^^ ^^ # true, if ! was given || # rest (should always be "" for valid dependencies) sub _split_dep { my $dep = shift; my ($pkg, $version, $darch) = ("", ["",""], [[],""]); $pkg = $1 if $dep =~ s/^\s*([^\s\[\(]+)\s*//; if (length $dep) { if ($dep =~ s/\s* \( \s* (<<|<=|<|=|>=|>>|>) \s* ([^\s(]+) \s* \) \s*//x) { @$version = ($1, $2); } if ($dep && $dep =~ s/\s*\[([^\]]+)\]\s*//) { my $t = $1; $darch->[1] = 1 if ($t =~ s/!//g); $darch->[0] = [ split /\s+/, $t ]; } } return ($pkg, $version, $darch, $dep); } sub _valid_version { my $ver = shift; # epoch check means nothing here... This check is only useful to detect # weird characters in version (and to get the debian revision) if ($ver =~ m/^(\d+:)?([-\.+:~A-Z0-9]+?)(-[\.+~A-Z0-9]+)?$/i) { return ($1, $2, $3); } else { return (); } } sub perl_core_has_version { my ($package, $op, $version) = @_; my $core_version = $PERL_CORE_PROVIDES->value($package); return 0 if !defined $core_version; my @version = _valid_version($version); return 0 if !@version; return versions_compare($core_version, $op, $version); } sub unfold { my $field = shift; my $line = shift; $$line =~ s/\n$//; if ($$line =~ s/\n//g) { tag "multiline-field", "$field"; } } 1; # Local Variables: # indent-tabs-mode: t # cperl-indent-level: 8 # End: # vim: syntax=perl sw=4 ts=4 noet shiftround