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

Re: dpkg-checkbuilddeps to generate a frontend friendly package list



--cut--
> | Actually do what we have been asked for. If anyone finds any design
> | flaw with that, please let me know ;-) That is also fairly trivial to
> | implement, the hard part is the sound design.
> 
> AFAIK, sbuild will just pick the first one in the list, whether it's
> available or not.  There is some value in using the same algorithm as
> sbuild.
> 
> [...]
> 
> | > I think this is fine.  It solves 95% of the cases people are interested
> | > in.
> |
> | If we can have 100% on board, why not just have it. I guess I just need
> | to sit down and add code to address OR'ed build-deps
> | (will do that, this weekend, eventually)
> 
> Because you add overhead for everybody if you start loading the package
> lists for each invocation of dpkg-checkbuilddeps.  This isn't so much of
> a problem on amd64 and other fast arches as it is on slow arches like
> mipsel or avr32.

The patch is not extremely tested yet, partially because the recent Dpkg API 
change forced me to build and install temporary dpkg package from recent 
master or there is an easier way out I'm missing here?

Both -s best-effort (just package names) and -f reliable (also checked for 
availability) algorithms are implemented as different options. Note that AptPkg 
is only engaged on demand, and it not intended to be listed in dpkg-dev 
depends.

Well, comments and critique welcome ;-) If you still think that AptPkg part is 
useless, you can cut that if you want, though I intend to use that even as my 
local branch of that tool.


diff --git a/scripts/dpkg-checkbuilddeps.pl b/scripts/dpkg-checkbuilddeps.pl
index 31b7ee6..c22faf6 100755
--- a/scripts/dpkg-checkbuilddeps.pl
+++ b/scripts/dpkg-checkbuilddeps.pl
@@ -47,6 +47,10 @@ sub usage {
                  retrieving them from control file
   -c build-conf  use given string for build conflicts instead of
                  retrieving them from control file
+  -f frontend    show frontend friendly list for build-depends and build-
conflicts
+                 indep included, also checking their cache availability 
(reliable list)
+  -s simplefr    show frontend friendly list for build-depends and build-
conflicts
+                 indep included, wihtout checking their cache availability 
(best effort list)
   --admindir=<directory>
                  change the administrative directory.
   -h, --help     show this help message.
@@ -57,17 +61,42 @@ sub usage {
 }
 
 my $binary_only=0;
-my ($bd_value, $bc_value);
+my ($bd_value, $bc_value, $frontend, $frontend_simple);
 if (!GetOptions('B' => \$binary_only,
                 'help|h' => sub { usage(); exit(0); },
                 'version' => \&version,
                 'd=s' => \$bd_value,
                 'c=s' => \$bc_value,
+                'f' => \$frontend,
+                's' => \$frontend_simple,
                 'admindir=s' => \$admindir)) {
 	usage();
 	exit(2);
 }
 
+# $frontend_simple just spits package names list ORed bdeps included, while 
+# $frontend also checks their availability via AptPkg which is engaged on 
demand
+if ($frontend and $frontend_simple) {
+    printf STDERR _g("%s: either use -f or -s\n"), $progname;    
+    exit 1;
+}
+
+my @feed_frontend;
+my ($_cache, $_sysver);
+if ($frontend) { # engage AptPkg machinery, which could be costly on some 
arches
+    eval { require AptPkg };
+    if ( $@ )  { die "-f requires libapt-pkg-perl package to be installed\n"; 
}
+    use AptPkg::Config '$_config';
+    use AptPkg::System '$_system';
+    use AptPkg::Cache;
+    use AptPkg::Version;
+    $_config->init;
+    $_config->{quiet} = 2;
+    $_system = $_config->system;
+    $_sysver = $_system->versioning;
+    $_cache = AptPkg::Cache->new;
+}
+
 my $controlfile = shift || "debian/control";
 
 my $control = Dpkg::Control::Info->new($controlfile);
@@ -101,15 +130,183 @@ if ($bc_value) {
 		deps_parse($bc_value, reduce_arch => 1, union => 1), $facts);
 }
 
+# Returns true if VER1 REL VER2 is true
+sub eval_ver_rel($ $ $) {
+    my ($ver1, $rel, $ver2) = @_;
+    my $cmp = $_sysver->compare($ver1, $ver2);
+
+    return 1
+    if (   ( ($rel eq "<=") && ( $cmp <= 0 ) )
+	or ( ($rel eq ">=") && ( $cmp >= 0 ) )
+	or ( ($rel eq "<<") && ( $cmp <  0 ) )
+	or ( ($rel eq ">>") && ( $cmp >  0 ) )
+	or ( ($rel eq "=")  && ( $cmp == 0 ) )
+    );
+    return 0;    
+}
+
+# Process one (versioned) build-dependency. 
+# OR'ed build dependencies are splitted earlier.
+# Returns true if package or package (relation version) is available, false 
otherwise.
+sub feed_bd_list($) {
+    my $bd = shift;
+    if ( $bd =~ /^(\S+)\s*\(\s*([><=]{1,2})\s*(\S+)\)\s*$/ ) { # package 
(relation version)
+	my ($pkg, $rel, $ver_control) = ($1, $2, $3);
+	if ($frontend_simple) {
+	    push @feed_frontend, $pkg;
+	    return 1;
+	}
+	my $pc = $_cache->{$pkg};
+	unless ($pc) {
+	    printf STDERR _g("Unknown package %s\n"), $pkg;	
+	    next;
+	}
+	my $current_state = $pc->{CurrentState};
+	my $available     = $pc->{VersionList};
+	if ( $available && ($current_state ne "Installed") ) {
+	    my $found_avail = 0;
+            # now walk the cached avail versions for that package
+	    for my $v (@$available) {
+		if ( eval_ver_rel($v->{VerStr}, $rel, $ver_control) ) {
+		    $found_avail = 1;
+		    last;
+		}
+	    }
+	    if ($found_avail) {
+		push @feed_frontend, $pkg;
+		return 1;
+	    }
+	    else {
+		printf STDERR _g("Can not satisfy build dependency %s %s %s. Maybe apt-get 
update?\n"), $pkg, $rel, $ver_control;
+		exit 1;
+	    }
+	}
+    }
+    elsif ( $bd =~ /^(\S+)$/ )  { # package without relation and version
+	if ($frontend_simple) {
+	    push @feed_frontend, $_;
+	    return 1;
+	}
+	my $pc = $_cache->{$_};   # = $1
+	unless ($pc) {
+	    printf STDERR _g("Unknown package %s. Maybe apt-get update?\n"), $_;
+	    exit 1;
+	}
+
+	push @feed_frontend, $bd;
+	return 1;
+    }
+    else {
+	printf STDERR _g("Unknown Build-Depends format\n");
+	exit 1;
+    }
+
+    # nothing found
+    return 0;
+}
+
+# Process the whole build-conflicts array at once. 
+# OR'ed conflicts don't make sense, which is checked earlier.
+# STDERRs on unknown packages listed in Build-Conflicts, but keeps going 
anyway
+# Returns void
+sub feed_bc_list($) {
+    my $rf = shift;
+    my @ar = @$rf;
+    for (@ar) {
+	if ( /^(\S+)\s*\(\s*([><=]{1,2})\s*(\S+)\)\s*$/ ) { # package (relation 
version)
+	    my ($pkg, $rel, $ver_control) = ($1, $2, $3);
+	    if ($frontend_simple) {
+		push @feed_frontend, $pkg;
+		return 1;
+	    }
+	    my $pc = $_cache->{$pkg};
+	    unless ($pc) {
+		printf STDERR _g("Unknown package %s\n"), $pkg;	
+		next;
+	    }
+	    my $current_state = $pc->{CurrentState};
+	    if ($pc->{CurrentVer}) {
+		my $ver_system = $pc->{CurrentVer}{VerStr};
+		push @feed_frontend, $pkg."-" if ( eval_ver_rel($ver_system, $rel, 
$ver_control)
+		    && ( $current_state ne "NotInstalled" )
+		)
+	    }
+	}
+	elsif ( /^(\S+)$/ )  { # package without relation and version
+	    if ($frontend_simple) {
+		push @feed_frontend, $_; # = $1
+		return 1;
+	    }
+	    my $pc = $_cache->{$_}; # = $1
+	    unless ($pc) {
+		printf STDERR _g("Unknown package %s\n"), $_;	
+		next;
+	    }
+	    push @feed_frontend, $_."-";
+	}
+	else {
+	    printf STDERR _g("Unknown Build-Conflicts format\n");
+	    exit 1;
+	}	
+    }
+}
+
 if (@unmet) {
-	printf STDERR _g("%s: Unmet build dependencies: "), $progname;
-	print STDERR join(" ", map { $_->output() } @unmet), "\n";
+    if ($frontend || $frontend_simple) {
+	my @pv_pairs = map { $_->dump() } @unmet;
+
+	# Build-Depends{-Indep}: a (rel X) | b (rel Y) | c, d, e (rel Z)
+	BD_LIST: 
+	  for (@unmet) {
+	      my @spt = split( '\|', $_);
+	      # process both: single, and several OR'ed build dependencies
+	      # pick the first one available which satisfies the given relation if any
+	      if ( @spt >= 1) {
+		  for (@spt) {
+		      s/^\s*//; s/\s*$//;
+		      next BD_LIST if ( feed_bd_list($_) );
+		  }
+		  (@spt == 1) ? 
+		    printf STDERR _g("%s: Unsatifyable build dependency: %s\n"), $progname, 
$_ :
+		    printf STDERR _g("%s: Unsatifyable ORed build dependencies: %s\n"), 
$progname, $_ ;
+		  exit 1;
+	      }
+	      else {
+		  die "Should never happen\n";
+	      }
+	  }
+    }
+    else {
+	printf STDERR _g("%s: Unmet build dependencies: "), $progname;  
+	print STDERR join(" ", map { $_->dump() } @unmet), "\n";
+    }
 }
+
 if (@conflicts) {
+    if ($frontend || $frontend_simple) {
+	my @pv_pairs = map { $_->dump() } @conflicts;
+	for (@conflicts) {
+	    my @spt = split( '\|', $_);
+	    if (@spt > 1) {
+		printf STDERR _g("%s: ORed build conflicts are not allowed: %s\n"), 
$progname, $_ ;
+		exit 1;
+	    }
+	}
+	feed_bc_list(\@pv_pairs);
+    }
+    else {
 	printf STDERR _g("%s: Build conflicts: "), $progname;
-	print STDERR join(" ", map { $_->output() } @conflicts), "\n";
+	print STDERR join(" ", map { $_->dump() } @conflicts), "\n";
+    }
+}
+
+if ( $frontend || $frontend_simple and scalar @feed_frontend) {
+    print STDOUT join (" ", @feed_frontend), "\n";
+    exit 0;
 }
-exit 1 if @unmet || @conflicts;
+else {
+    exit 1 if @unmet || @conflicts;
+};
 
 # Silly little status file parser that returns a Dpkg::Deps::KnownFacts
 sub parse_status {


-- 
pub 4096R/0E4BD0AB <people.fccf.net/danchev/key pgp.mit.edu>
diff --git a/scripts/dpkg-checkbuilddeps.pl b/scripts/dpkg-checkbuilddeps.pl
index 31b7ee6..c22faf6 100755
--- a/scripts/dpkg-checkbuilddeps.pl
+++ b/scripts/dpkg-checkbuilddeps.pl
@@ -47,6 +47,10 @@ sub usage {
                  retrieving them from control file
   -c build-conf  use given string for build conflicts instead of
                  retrieving them from control file
+  -f frontend    show frontend friendly list for build-depends and build-conflicts
+                 indep included, also checking their cache availability (reliable list)
+  -s simplefr    show frontend friendly list for build-depends and build-conflicts
+                 indep included, wihtout checking their cache availability (best effort list)
   --admindir=<directory>
                  change the administrative directory.
   -h, --help     show this help message.
@@ -57,17 +61,42 @@ sub usage {
 }
 
 my $binary_only=0;
-my ($bd_value, $bc_value);
+my ($bd_value, $bc_value, $frontend, $frontend_simple);
 if (!GetOptions('B' => \$binary_only,
                 'help|h' => sub { usage(); exit(0); },
                 'version' => \&version,
                 'd=s' => \$bd_value,
                 'c=s' => \$bc_value,
+                'f' => \$frontend,
+                's' => \$frontend_simple,
                 'admindir=s' => \$admindir)) {
 	usage();
 	exit(2);
 }
 
+# $frontend_simple just spits package names list ORed bdeps included, while 
+# $frontend also checks their availability via AptPkg which is engaged on demand
+if ($frontend and $frontend_simple) {
+    printf STDERR _g("%s: either use -f or -s\n"), $progname;    
+    exit 1;
+}
+
+my @feed_frontend;
+my ($_cache, $_sysver);
+if ($frontend) { # engage AptPkg machinery, which could be costly on some arches
+    eval { require AptPkg };
+    if ( $@ )  { die "-f requires libapt-pkg-perl package to be installed\n"; }
+    use AptPkg::Config '$_config';
+    use AptPkg::System '$_system';
+    use AptPkg::Cache;
+    use AptPkg::Version;
+    $_config->init;
+    $_config->{quiet} = 2;
+    $_system = $_config->system;
+    $_sysver = $_system->versioning;
+    $_cache = AptPkg::Cache->new;
+}
+
 my $controlfile = shift || "debian/control";
 
 my $control = Dpkg::Control::Info->new($controlfile);
@@ -101,15 +130,183 @@ if ($bc_value) {
 		deps_parse($bc_value, reduce_arch => 1, union => 1), $facts);
 }
 
+# Returns true if VER1 REL VER2 is true
+sub eval_ver_rel($ $ $) {
+    my ($ver1, $rel, $ver2) = @_;
+    my $cmp = $_sysver->compare($ver1, $ver2);
+
+    return 1
+    if (   ( ($rel eq "<=") && ( $cmp <= 0 ) )
+	or ( ($rel eq ">=") && ( $cmp >= 0 ) )
+	or ( ($rel eq "<<") && ( $cmp <  0 ) )
+	or ( ($rel eq ">>") && ( $cmp >  0 ) )
+	or ( ($rel eq "=")  && ( $cmp == 0 ) )
+    );
+    return 0;    
+}
+
+# Process one (versioned) build-dependency. 
+# OR'ed build dependencies are splitted earlier.
+# Returns true if package or package (relation version) is available, false otherwise.
+sub feed_bd_list($) {
+    my $bd = shift;
+    if ( $bd =~ /^(\S+)\s*\(\s*([><=]{1,2})\s*(\S+)\)\s*$/ ) { # package (relation version)
+	my ($pkg, $rel, $ver_control) = ($1, $2, $3);
+	if ($frontend_simple) {
+	    push @feed_frontend, $pkg;
+	    return 1;
+	}
+	my $pc = $_cache->{$pkg};
+	unless ($pc) {
+	    printf STDERR _g("Unknown package %s\n"), $pkg;	
+	    next;
+	}
+	my $current_state = $pc->{CurrentState};
+	my $available     = $pc->{VersionList};
+	if ( $available && ($current_state ne "Installed") ) {
+	    my $found_avail = 0;
+            # now walk the cached avail versions for that package
+	    for my $v (@$available) {
+		if ( eval_ver_rel($v->{VerStr}, $rel, $ver_control) ) {
+		    $found_avail = 1;
+		    last;
+		}
+	    }
+	    if ($found_avail) {
+		push @feed_frontend, $pkg;
+		return 1;
+	    }
+	    else {
+		printf STDERR _g("Can not satisfy build dependency %s %s %s. Maybe apt-get update?\n"), $pkg, $rel, $ver_control;
+		exit 1;
+	    }
+	}
+    }
+    elsif ( $bd =~ /^(\S+)$/ )  { # package without relation and version
+	if ($frontend_simple) {
+	    push @feed_frontend, $_;
+	    return 1;
+	}
+	my $pc = $_cache->{$_};   # = $1
+	unless ($pc) {
+	    printf STDERR _g("Unknown package %s. Maybe apt-get update?\n"), $_;
+	    exit 1;
+	}
+
+	push @feed_frontend, $bd;
+	return 1;
+    }
+    else {
+	printf STDERR _g("Unknown Build-Depends format\n");
+	exit 1;
+    }
+
+    # nothing found
+    return 0;
+}
+
+# Process the whole build-conflicts array at once. 
+# OR'ed conflicts don't make sense, which is checked earlier.
+# STDERRs on unknown packages listed in Build-Conflicts, but keeps going anyway
+# Returns void
+sub feed_bc_list($) {
+    my $rf = shift;
+    my @ar = @$rf;
+    for (@ar) {
+	if ( /^(\S+)\s*\(\s*([><=]{1,2})\s*(\S+)\)\s*$/ ) { # package (relation version)
+	    my ($pkg, $rel, $ver_control) = ($1, $2, $3);
+	    if ($frontend_simple) {
+		push @feed_frontend, $pkg;
+		return 1;
+	    }
+	    my $pc = $_cache->{$pkg};
+	    unless ($pc) {
+		printf STDERR _g("Unknown package %s\n"), $pkg;	
+		next;
+	    }
+	    my $current_state = $pc->{CurrentState};
+	    if ($pc->{CurrentVer}) {
+		my $ver_system = $pc->{CurrentVer}{VerStr};
+		push @feed_frontend, $pkg."-" if ( eval_ver_rel($ver_system, $rel, $ver_control)
+		    && ( $current_state ne "NotInstalled" )
+		)
+	    }
+	}
+	elsif ( /^(\S+)$/ )  { # package without relation and version
+	    if ($frontend_simple) {
+		push @feed_frontend, $_; # = $1
+		return 1;
+	    }
+	    my $pc = $_cache->{$_}; # = $1
+	    unless ($pc) {
+		printf STDERR _g("Unknown package %s\n"), $_;	
+		next;
+	    }
+	    push @feed_frontend, $_."-";
+	}
+	else {
+	    printf STDERR _g("Unknown Build-Conflicts format\n");
+	    exit 1;
+	}	
+    }
+}
+
 if (@unmet) {
-	printf STDERR _g("%s: Unmet build dependencies: "), $progname;
-	print STDERR join(" ", map { $_->output() } @unmet), "\n";
+    if ($frontend || $frontend_simple) {
+	my @pv_pairs = map { $_->dump() } @unmet;
+
+	# Build-Depends{-Indep}: a (rel X) | b (rel Y) | c, d, e (rel Z)
+	BD_LIST: 
+	  for (@unmet) {
+	      my @spt = split( '\|', $_);
+	      # process both: single, and several OR'ed build dependencies
+	      # pick the first one available which satisfies the given relation if any
+	      if ( @spt >= 1) {
+		  for (@spt) {
+		      s/^\s*//; s/\s*$//;
+		      next BD_LIST if ( feed_bd_list($_) );
+		  }
+		  (@spt == 1) ? 
+		    printf STDERR _g("%s: Unsatifyable build dependency: %s\n"), $progname, $_ :
+		    printf STDERR _g("%s: Unsatifyable ORed build dependencies: %s\n"), $progname, $_ ;
+		  exit 1;
+	      }
+	      else {
+		  die "Should never happen\n";
+	      }
+	  }
+    }
+    else {
+	printf STDERR _g("%s: Unmet build dependencies: "), $progname;  
+	print STDERR join(" ", map { $_->dump() } @unmet), "\n";
+    }
 }
+
 if (@conflicts) {
+    if ($frontend || $frontend_simple) {
+	my @pv_pairs = map { $_->dump() } @conflicts;
+	for (@conflicts) {
+	    my @spt = split( '\|', $_);
+	    if (@spt > 1) {
+		printf STDERR _g("%s: ORed build conflicts are not allowed: %s\n"), $progname, $_ ;
+		exit 1;
+	    }
+	}
+	feed_bc_list(\@pv_pairs);
+    }
+    else {
 	printf STDERR _g("%s: Build conflicts: "), $progname;
-	print STDERR join(" ", map { $_->output() } @conflicts), "\n";
+	print STDERR join(" ", map { $_->dump() } @conflicts), "\n";
+    }
+}
+
+if ( $frontend || $frontend_simple and scalar @feed_frontend) {
+    print STDOUT join (" ", @feed_frontend), "\n";
+    exit 0;
 }
-exit 1 if @unmet || @conflicts;
+else {
+    exit 1 if @unmet || @conflicts;
+};
 
 # Silly little status file parser that returns a Dpkg::Deps::KnownFacts
 sub parse_status {

Reply to: