[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#740607: lintian: Please support build-profiles



Hi,

Quoting Niels Thykier (2014-03-04 22:51:12)
> Thanks for confirming my assertion.  Sadly, I see I phrased myself poorly.
> You implemented what I said, but not what I wanted.  What I meant was to
> remove the assignment to $d_restr completely.  I.e.
> 
>   my ($d_pkg, $d_march, $d_version, $d_arch, undef, $rest,
>       $part_d_orig)
>     = @$part_d;
> 
> It is a little Perl feature (similar to the use of "_" in some high
> level programming languages to signal "I don't care about this
> argument/value").  It has the advantage of ensuring that $d_restr is not
> declared in the scope and make any use of it a fatal error (as a
> compile-time check \o/).

ah right, that makes much more sense :) Yes, I'm familiar with "_" from python.
Done.

> Not necessarily.  We can have "multi-level" data files.  I suspect that
> commit 6bca8f4830f95e27657c70d45b7ec6003dbe3673 could be an example to get
> you started.  Alternatively, look at $MENU_SECTIONS in checks/menu-format.pm
> 
> I am thinking something simple like:
> 
> data/fields/known-build-profiles:
>   """
>   profile.stage1
>   profile.notest
>   ...
>   """
> 
> and a:
> 
> sub _parse_build_profiles_data{
>     my ($key, $val, $pval) = @_;
>     [... see commit 6bca8f4 for inspiration here ...]
> }
> 
> my $KNOWN_BUILD_PROFILES = Lintian::Data->new(
>   'fields/known-build-profiles', qr/\./,
>   \&_parse_build_profiles_data);

Okay. I think I managed to come up with a solution.

> I suppose it would make sense to rename it, but I am not quite convinced it
> is worth the hassle.  If anything, add the new constructor and make
> new_noarch an alias of that.  I believe the syntax is something like:
> *new_noarch = \&new_norestriction
> 
> (see Lintian::Util where we do it with open_gz).

I also implemented that now.

> > Though now that I look at the logic, there might be a simple way to test
> > for implication without checking N^2 possibilities. I'll look into it.
> 
> Sounds good! \o/  Anyhow, this part can certainly come in a later patch.

It turns out that I need more free time than I currently have to tackle the
problem. I want to transform build profiles into normal logic so that I can
prove implication using formal methods on a piece of paper. I did not have a
head clear enough for that in the past few weeks so I would like to take you up
on your offer and deliver that functionality in a later patch. Since our GSoC
students started working on a few packages it has become clear that basic
lintian support as implemented in the past few patches would've avoided a
couple of common errors the students made.

Before you apply the attached patch I have another question. Can lintian also
check debian/control in addition to DEBIAN/control? Currently, dpkg does not
forward the contents of the Build-Profiles field in debian/control to
DEBIAN/control and we are currently discussing if it makes sense to do so with
Guillem. If lintian can't check debian/control, then never mind. But if it can,
then I would like to add a few additional checks. In that case, can you give me
an example of a tag that checks debian/control?

cheers, josch
From a5da9dcec6dcb6a86a33a293c1ce5598665d969e Mon Sep 17 00:00:00 2001
From: josch <j.schauer@email.de>
Date: Mon, 3 Mar 2014 15:58:28 +0100
Subject: [PATCH] Build-profiles support

 - added 3 new tags to detect errors in restriction list syntax
     * invalid-restriction-term-in-source-relation
     * invalid-restriction-namespace-in-source-relation
     * invalid-restriction-label-in-source-relation
 - added 4 new tags to ensure that if restrictions lists are used, a
   versioned build dependency on dpkg-dev and (if applicable) debhelper
   is added and no conflicts with them exist
     * restriction-list-without-versioned-dpkg-dev-dependency
     * restriction-list-with-versioned-dpkg-dev-conflict
     * restriction-list-with-debhelper-without-debhelper-version
     * restriction-list-with-debhelper-with-conflicting-debhelper-version
 - added data/fields/dependency-restrictions and a parser to keep record
   of valid namespaces and labels for restriction lists
---
 checks/fields.desc                                 | 50 ++++++++++++++
 checks/fields.pm                                   | 80 +++++++++++++++++++---
 data/fields/dependency-restrictions                |  4 ++
 lib/Lintian/Relation.pm                            | 47 +++++++++----
 .../debian/debian/control.in                       |  7 +-
 t/tests/fields-build-depends-general/desc          |  7 ++
 t/tests/fields-build-depends-general/tags          |  7 ++
 7 files changed, 179 insertions(+), 23 deletions(-)
 create mode 100644 data/fields/dependency-restrictions

diff --git a/checks/fields.desc b/checks/fields.desc
index d55c11a..6061b95 100644
--- a/checks/fields.desc
+++ b/checks/fields.desc
@@ -637,6 +637,56 @@ Info: The architecture string in this source relation has some
  negated.  This is not permitted by Policy.  Either all architectures must
  be negated or none of them may be.
 
+Tag: invalid-restriction-term-in-source-relation
+Severity: important
+Certainty: certain
+Info: The restriction list in the source relation includes a term which
+ does not contain exactly one dot separating restriction namespace and label.
+ A term in a restriction list is of the form "namespace.label" (without the
+ quotes).
+
+Tag: invalid-restriction-namespace-in-source-relation
+Severity: important
+Certainty: possible
+Info: The restriction list in the source relation includes a term with
+ an unknown namespace. The only allowed namespace is "profile" (without the
+ quotes).
+
+Tag: invalid-restriction-label-in-source-relation
+Severity: important
+Certainty: possible
+Info: The restriction list in the source relation includes a term with
+ an unknown label. The only allowed labels are "stage1", "stage2", "notest"
+ and "cross".
+
+Tag: restriction-list-without-versioned-dpkg-dev-dependency
+Severity: normal
+Certainty: certain
+Info: If a restriction list appears in the build dependencies, then the
+ source package has to build depend on dpkg-dev (>= 1.17.2) for minimal
+ restriction list support.
+
+Tag: restriction-list-with-versioned-dpkg-dev-conflict
+Severity: normal
+Certainty: certain
+Info: If a restriction list appears in the build dependencies, then the
+ source package has to build depend on dpkg-dev (>= 1.17.2) for minimal
+ restriction list support. It must not conflict with version 1.17.2.
+
+Tag: restriction-list-with-debhelper-without-debhelper-version
+Severity: normal
+Certainty: certain
+Info: If a restriction list appears in the build dependencies and the
+ package uses debhelper, then the source package has to depend on at least
+ debhelper 9.20140227.
+
+Tag: restriction-list-with-debhelper-with-conflicting-debhelper-version
+Severity: normal
+Certainty: certain
+Info: If a restriction list appears in the build dependencies and the
+ package uses debhelper, then the source package has to depend on at least
+ debhelper 9.20140227. It must not conflict with version 9.20140227.
+
 Tag: depends-on-build-essential-package-without-using-version
 Severity: important
 Certainty: certain
diff --git a/checks/fields.pm b/checks/fields.pm
index aeee252..12f2dbb 100644
--- a/checks/fields.pm
+++ b/checks/fields.pm
@@ -46,6 +46,9 @@ our $known_build_essential
   = Lintian::Data->new('fields/build-essential-packages');
 our $KNOWN_BINARY_FIELDS = Lintian::Data->new('fields/binary-fields');
 our $KNOWN_UDEB_FIELDS = Lintian::Data->new('fields/udeb-fields');
+our $KNOWN_DEPENDENCY_RESTRICTIONS
+  = Lintian::Data->new('fields/dependency-restrictions',
+      qr/\./, \&_load_dependency_restrictions);
 
 our %KNOWN_ARCHIVE_PARTS = map { $_ => 1 } ('non-free', 'contrib');
 
@@ -730,7 +733,7 @@ sub run {
                     && $known_obsolete_emacs{$alternatives[0]->[0]});
 
                 for my $part_d (@alternatives) {
-                    my ($d_pkg, $d_march, $d_version, $d_arch, $rest,
+                    my ($d_pkg, $d_march, $d_version, $d_arch, undef, $rest,
                         $part_d_orig)
                       = @$part_d;
 
@@ -935,6 +938,7 @@ sub run {
             any { $_ eq $_[0] } qw(build-depends build-depends-indep);
         };
 
+        my $restrictions_used = 0;
         my %depend;
         for my $field (
             qw(build-depends build-depends-indep build-conflicts build-conflicts-indep)
@@ -956,7 +960,7 @@ sub run {
                         && &$is_dep_field($field));
 
                     for my $part_d (@alternatives) {
-                        my ($d_pkg, $d_march, $d_version, $d_arch, $rest,
+                        my ($d_pkg, $d_march, $d_version, $d_arch, $d_restr, $rest,
                             $part_d_orig)
                           = @$part_d;
 
@@ -968,6 +972,30 @@ sub run {
                             }
                         }
 
+                        if (scalar @{$d_restr} >= 1) {
+                            $restrictions_used = 1;
+                        }
+
+                        for my $restr (@{$d_restr}) {
+                            my $dotcount = $restr =~ tr/.//;
+                            if ($dotcount != 1) {
+                                tag 'invalid-restriction-term-in-source-relation',
+                                  "$restr [$field: $part_d_orig]";
+                                next;
+                            }
+                            $restr =~ s/^!//;
+                            my ($ns, $label) = split(/\./, $restr, 2);
+                            if ($KNOWN_DEPENDENCY_RESTRICTIONS->known($ns)) {
+                                tag 'invalid-restriction-label-in-source-relation',
+                                  "$label [$field: $part_d_orig]"
+                                  unless any { $_ eq $label }
+                                  @{$KNOWN_DEPENDENCY_RESTRICTIONS->value($ns)};
+                            } else {
+                                tag 'invalid-restriction-namespace-in-source-relation',
+                                  "$ns [$field: $part_d_orig]";
+                            }
+                        }
+
                         if (   $d_pkg =~ m/^openjdk-\d+-doc$/o
                             or $d_pkg eq 'classpath-doc'){
                             tag 'build-depends-on-specific-java-doc-package',
@@ -1077,6 +1105,24 @@ sub run {
             }
         }
 
+        # if restrictions are found in the build-depends/conflicts, then 
+        # package must build-depend on dpkg (>= 1.17.2)
+        if ($restrictions_used) {
+            my $build_conflicts_all = $info->relation('build-conflicts-all');
+            tag 'restriction-list-without-versioned-dpkg-dev-dependency'
+              unless ($build_all->implies('dpkg-dev (>= 1.17.2)'));
+            tag 'restriction-list-with-versioned-dpkg-dev-conflict'
+              if ($build_conflicts_all->implies_inverse('dpkg-dev (<< 1.17.2)'));
+            # if the package uses debhelper then it must require and not
+            # conflict with version >= 9.20140227
+            if ($build_all->implies('debhelper')) {
+                tag 'restriction-list-with-debhelper-without-debhelper-version'
+                  unless ($build_all->implies('debhelper (>= 9.20140227)'));
+                tag 'restriction-list-with-debhelper-with-conflicting-debhelper-version'
+                  if ($build_conflicts_all->implies_inverse('debhelper (<< 9.20140227)'));
+            }
+        }
+
         my (@arch_dep_pkgs, @dbg_pkgs);
         foreach my $gproc ($group->get_binary_processables) {
             my $binpkg = $gproc->pkg_name;
@@ -1238,16 +1284,16 @@ sub run {
     return;
 }
 
-# splits "foo:bar (>= 1.2.3) [!i386 ia64]" into
-# ( "foo", "bar", [ ">=", "1.2.3" ], [ [ "i386", "ia64" ], 1 ], "" )
-#                                                         ^^^   ^^
-#                     count of negated arches, if ! was given   ||
-#                  rest (should always be "" for valid dependencies)
+# splits "foo:bar (>= 1.2.3) [!i386 ia64] <!profile.stage1 !profile.notest>" into
+# ( "foo", "bar", [ ">=", "1.2.3" ], [ [ "i386", "ia64" ], 1 ], [ "!profile.stage1" "!profile.notest" ], "" )
+#                                                         ^^^                                            ^^
+#                     count of negated arches, if ! was given                                            ||
+#                                                           rest (should always be "" for valid dependencies)
 sub _split_dep {
     my $dep = shift;
-    my ($pkg, $dmarch, $version, $darch) = ('', '', ['',''], [[], 0]);
+    my ($pkg, $dmarch, $version, $darch, $restr) = ('', '', ['',''], [[], 0], []);
 
-    my $pkgname = $1 if $dep =~ s/^\s*([^\s\[\(]+)\s*//;
+    my $pkgname = $1 if $dep =~ s/^\s*([^<\s\[\(]+)\s*//;
     if (defined $pkgname) {
         ($pkg, $dmarch) = split /:/, $pkgname, 2;
         $dmarch //= '';  # Ensure it is defined (in case there is no ":")
@@ -1267,9 +1313,23 @@ sub _split_dep {
             }
             $darch->[1] = $negated;
         }
+        if ($dep && $dep =~ s/\s*<([^>]+)>\s*//) {
+            my $t = $1;
+            $restr = [split /\s+/, $t];
+        }
     }
 
-    return ($pkg, $dmarch, $version, $darch, $dep);
+    return ($pkg, $dmarch, $version, $darch, $restr, $dep);
+}
+
+sub _load_dependency_restrictions {
+    my ($key, $value, $pval) = @_;
+    my $ret = undef;
+    if (not defined $pval) {
+        $ret = $pval = [];
+    }
+    push @{$pval}, $value;
+    return $ret;
 }
 
 sub perl_core_has_version {
diff --git a/data/fields/dependency-restrictions b/data/fields/dependency-restrictions
new file mode 100644
index 0000000..270be3c
--- /dev/null
+++ b/data/fields/dependency-restrictions
@@ -0,0 +1,4 @@
+profile.notest
+profile.stage1
+profile.stage2
+profile.cross
diff --git a/lib/Lintian/Relation.pm b/lib/Lintian/Relation.pm
index 1ba783c..6d72176 100644
--- a/lib/Lintian/Relation.pm
+++ b/lib/Lintian/Relation.pm
@@ -115,21 +115,26 @@ sub parse_element {
          \s* (.*?)                      # architectures (5)
          \s* \]                         # closing bracket
         )?                              # end of optional architecture
+        (?:                             # start of optional restriction
+          \s* <                         # open bracket for restriction
+          \s* (.*?)                     # don't parse restrictions now
+          \s* >                         # closing bracket
+        )?                              # end of optional restriction
     /x;
 
-    my ($pkgname, $march, $relop, $relver, $bdarch) = ($1, $2, $3, $4, $5);
+    my ($pkgname, $march, $relop, $relver, $bdarch, $restr) = ($1, $2, $3, $4, $5, $6);
     my @array;
     if (not defined($relop)) {
         # If there's no version, we don't need to do any further processing.
         # Otherwise, convert the legacy < and > relations to the current ones.
-        @array = ('PRED', $pkgname, undef, undef, $bdarch, $march);
+        @array = ('PRED', $pkgname, undef, undef, $bdarch, $march, $restr);
     } else {
         if ($relop eq '<') {
             $relop = '<<';
         } elsif ($relop eq '>') {
             $relop = '>>';
         }
-        @array = ('PRED', $pkgname, $relop, $relver, $bdarch, $march);
+        @array = ('PRED', $pkgname, $relop, $relver, $bdarch, $march, $restr);
     }
 
     # Optimise the memory usage of the array.  Understanding this
@@ -191,26 +196,37 @@ sub new {
     return $self;
 }
 
-=item new_noarch(RELATION)
+=item new_norestriction(RELATION)
 
 Creates a new Lintian::Relation object corresponding to the parsed
-relationship RELATION, ignoring architecture restrictions.  This should be
-used in cases where we only care if a dependency is present in some cases
-and we don't want to require that the architectures match (such as when
-checking for proper build dependencies, since if there are architecture
-constraints the maintainer is doing something beyond Lintian's ability to
-analyze).  RELATION may be C<undef> or the empty string, in which case the
-returned Lintian::Relation object is empty (always satisfied).
+relationship RELATION, ignoring architecture restrictions and restriction
+lists. This should be used in cases where we only care if a dependency is
+present in some cases and we don't want to require that the architectures
+match (such as when checking for proper build dependencies, since if there
+are architecture constraints the maintainer is doing something beyond
+Lintian's ability to analyze) or that the restrictions list match (Lintian
+can't handle dependency implications with build profiles yet).  RELATION
+may be C<undef> or the empty string, in which case the returned
+Lintian::Relation object is empty (always satisfied).
 
 =cut
 
-sub new_noarch {
+sub new_norestriction {
     my ($class, $relation) = @_;
     $relation = '' unless defined($relation);
     $relation =~ s/\[[^\]]*\]//g;
+    $relation =~ s/<[^>]*>//g;
     return $class->new($relation);
 }
 
+=item new_noarch(RELATION)
+
+An alias for new_norestriction.
+
+=cut
+
+*new_noarch = \&new_norestriction;
+
 =item and(RELATION, ...)
 
 Creates a new Lintian::Relation object produced by AND'ing all the
@@ -351,6 +367,13 @@ sub implies_element {
     $$q[1] = '' unless defined $$q[1];
     return if $$p[1] ne $$q[1];
 
+    # Since the restriction list is not a set (as the architecture list) there
+    # is no way to calculate a superset or subset of one another. Furthermore,
+    # the evaluation depends on which build profiles are currently activated.
+    # With n being the number of possible build profiles, 2^n checks would
+    # have to be done. We decide not to do that (yet).
+    return if defined $$p[6] or defined $$q[6];
+
     # If the names match, then the only difference is in the architecture or
     # version clauses.  First, check architecture.  The architectures for p
     # must be a superset of the architectures for q.
diff --git a/t/tests/fields-build-depends-general/debian/debian/control.in b/t/tests/fields-build-depends-general/debian/debian/control.in
index f6546b9..80222ef 100644
--- a/t/tests/fields-build-depends-general/debian/debian/control.in
+++ b/t/tests/fields-build-depends-general/debian/debian/control.in
@@ -8,8 +8,13 @@ Build-Depends: debhelper (>= 9), bd-conflict, revision-1 (>= 1.0-1),
  xorg-dev, java-propose-classpath, python3.2-dev, foo [all],
  bar [i386 any], baz [source i3!86], baz [i386 !amd64],
  other-pkg [kfreebsd-any], yet-another [any-powerpc],
+ big <profile.stage1>, bpfail1 <foobar>,
+ bpfail2 <foo.bar>, bpfail3 <profile.bar>,
  packaging-dev, libdb5.1++-dev, libdb5.1-java-dev
-Build-Conflicts: bd-conflict
+Build-Conflicts:
+ bd-conflict,
+ dpkg-dev (>= 1.17.2),
+ debhelper (>= 9.20140227)
 
 Package: {$source}
 Architecture: {$architecture}
diff --git a/t/tests/fields-build-depends-general/desc b/t/tests/fields-build-depends-general/desc
index b8b7a1b..712ebd7 100644
--- a/t/tests/fields-build-depends-general/desc
+++ b/t/tests/fields-build-depends-general/desc
@@ -16,5 +16,12 @@ Test-For:
  depends-on-build-essential-package-without-using-version
  depends-on-packaging-dev
  invalid-arch-string-in-source-relation
+ invalid-restriction-label-in-source-relation
+ invalid-restriction-namespace-in-source-relation
+ invalid-restriction-term-in-source-relation
  ored-build-depends-on-obsolete-package
+ restriction-list-with-debhelper-with-conflicting-debhelper-version
+ restriction-list-with-debhelper-without-debhelper-version
+ restriction-list-with-versioned-dpkg-dev-conflict
+ restriction-list-without-versioned-dpkg-dev-dependency
 References: Debian Bug#540594, Debian Bug#551793
diff --git a/t/tests/fields-build-depends-general/tags b/t/tests/fields-build-depends-general/tags
index 0c0222f..1bfb5eb 100644
--- a/t/tests/fields-build-depends-general/tags
+++ b/t/tests/fields-build-depends-general/tags
@@ -9,9 +9,16 @@ E: fields-build-depends-general source: depends-on-build-essential-package-witho
 E: fields-build-depends-general source: invalid-arch-string-in-source-relation all [build-depends: foo [all]]
 E: fields-build-depends-general source: invalid-arch-string-in-source-relation i3!86 [build-depends: baz [source i3!86]]
 E: fields-build-depends-general source: invalid-arch-string-in-source-relation source [build-depends: baz [source i3!86]]
+E: fields-build-depends-general source: invalid-restriction-label-in-source-relation bar [build-depends: bpfail3 <profile.bar>]
+E: fields-build-depends-general source: invalid-restriction-namespace-in-source-relation foo [build-depends: bpfail2 <foo.bar>]
+E: fields-build-depends-general source: invalid-restriction-term-in-source-relation foobar [build-depends: bpfail1 <foobar>]
 I: fields-build-depends-general source: build-depends-on-python-dev-with-no-arch-any
 I: fields-build-depends-general source: ored-build-depends-on-obsolete-package build-depends: xlibmesa-gl-dev
 W: fields-build-depends-general source: build-depends-on-1-revision build-depends: revision-1 (>= 1.0-1)
 W: fields-build-depends-general source: build-depends-on-versioned-berkeley-db build-depends:libdb5.1++-dev
 W: fields-build-depends-general source: build-depends-on-versioned-berkeley-db build-depends:libdb5.1-java-dev
 W: fields-build-depends-general source: depends-on-packaging-dev build-depends
+W: fields-build-depends-general source: restriction-list-with-debhelper-with-conflicting-debhelper-version
+W: fields-build-depends-general source: restriction-list-with-debhelper-without-debhelper-version
+W: fields-build-depends-general source: restriction-list-with-versioned-dpkg-dev-conflict
+W: fields-build-depends-general source: restriction-list-without-versioned-dpkg-dev-dependency
-- 
1.8.5.3


Reply to: