summaryrefslogtreecommitdiffstats
path: root/scripts/build-rdeps.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/build-rdeps.pl')
-rwxr-xr-xscripts/build-rdeps.pl558
1 files changed, 558 insertions, 0 deletions
diff --git a/scripts/build-rdeps.pl b/scripts/build-rdeps.pl
new file mode 100755
index 0000000..c741371
--- /dev/null
+++ b/scripts/build-rdeps.pl
@@ -0,0 +1,558 @@
+#!/usr/bin/perl
+# -*- tab-width: 4; indent-tabs-mode: t; cperl-indent-level: 4 -*-
+# vim: set ai shiftwidth=4 tabstop=4 expandtab:
+# Copyright (C) Patrick Schoenfeld
+# 2015 Johannes Schauer Marin Rodrigues <josch@debian.org>
+# 2017 James McCoy <jamessan@debian.org>
+#
+# 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+=head1 NAME
+
+build-rdeps - find packages that depend on a specific package to build (reverse build depends)
+
+=head1 SYNOPSIS
+
+B<build-rdeps> I<package>
+
+=head1 DESCRIPTION
+
+B<build-rdeps> searches for all packages that build-depend on the specified package.
+
+The default behaviour is to just `grep` for the given dependency in the
+Build-Depends field of apt's Sources files.
+
+If the package dose-extra >= 4.0 is installed, then a more complete reverse
+build dependency computation is carried out. In particular, with that package
+installed, build-rdeps will find transitive reverse dependencies, respect
+architecture and build profile restrictions, take Provides relationships,
+Conflicts, Pre-Depends, Build-Depends-Arch and versioned dependencies into
+account and correctly resolve multiarch relationships for crossbuild reverse
+dependency resolution. (This tends to be a slow process due to the complexity
+of the package interdependencies.)
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-u>, B<--update>
+
+Run apt-get update before searching for build-depends.
+
+=item B<-s>, B<--sudo>
+
+Use sudo when running apt-get update. Has no effect if -u is omitted.
+
+=item B<--distribution>
+
+Select another distribution, which is searched for build-depends.
+
+=item B<--only-main>
+
+Ignore contrib, non-free and non-free-firmware.
+
+=item B<--only-devel>
+
+Consider only development distributions (e.g. unstable, sid).
+
+=item B<--exclude-component>
+
+Ignore the given component (e.g. main, contrib, non-free, non-free-firmware).
+
+=item B<--origin>
+
+Restrict the search to only the specified origin (such as "Debian").
+
+=item B<-m>, B<--print-maintainer>
+
+Print the value of the maintainer field for each package.
+
+=item B<--host-arch>
+
+Explicitly set the host architecture. The default is the value of
+`dpkg-architecture -qDEB_HOST_ARCH`. This option only works if dose-extra >=
+4.0 is installed.
+
+=item B<--build-arch>
+
+Explicitly set the build architecture. The default is the value of
+`dpkg-architecture -qDEB_BUILD_ARCH`. This option only works if dose-extra >=
+4.0 is installed.
+
+=item B<--no-arch-all>, B<--no-arch-any>
+
+Ignore Build-Depends-Indep or Build-Depends-Arch while looking for reverse
+dependencies.
+
+=item B<--old>
+
+Force the old simple behaviour without dose-ceve support even if dose-extra >=
+4.0 is installed. (This tends to be faster.)
+
+Notice, that the old behaviour only finds direct dependencies, ignores virtual
+dependencies, does not find transitive dependencies and does not take version
+relationships, architecture restrictions, build profiles or multiarch
+relationships into account.
+
+=item B<-q>, B<--quiet>
+
+Don't print meta information (header, counter). Making it easier to use in
+scripts.
+
+=item B<-d>, B<--debug>
+
+Run the debug mode
+
+=item B<--help>
+
+Show the usage information.
+
+=item B<--version>
+
+Show the version information.
+
+=back
+
+=head1 REQUIREMENTS
+
+The tool requires apt Sources files to be around for the checked components.
+In the default case this means that in /var/lib/apt/lists files need to be
+around for main, contrib, non-free and non-free-firmware.
+
+In practice this means one needs to add one deb-src line for each component,
+e.g.
+
+deb-src http://<mirror>/debian <dist> main contrib non-free non-free-firmware
+
+and run apt-get update afterwards or use the update option of this tool.
+
+=cut
+
+use warnings;
+use strict;
+use File::Basename;
+use Getopt::Long qw(:config bundling permute no_getopt_compat);
+
+use Dpkg::Control;
+use Dpkg::Vendor qw(get_current_vendor);
+
+my $progname = basename($0);
+my $version = '1.0';
+my $use_ceve = 0;
+my $ceve_compatible;
+my $opt_debug;
+my $opt_update;
+my $opt_sudo;
+my $opt_maintainer;
+my $opt_mainonly;
+my $opt_develonly = 0;
+my $opt_distribution;
+my $opt_origin = 'Debian';
+my @opt_exclude_components;
+my $opt_buildarch;
+my $opt_hostarch;
+my $opt_without_ceve;
+my $opt_quiet;
+my $opt_noarchall;
+my $opt_noarchany;
+
+sub version {
+ print <<"EOT";
+This is $progname $version, from the Debian devscripts package, v. ###VERSION###
+This code is copyright by Patrick Schoenfeld, all rights reserved.
+It comes with ABSOLUTELY NO WARRANTY. You are free to redistribute this code
+under the terms of the GNU General Public License, version 2 or later.
+EOT
+ exit(0);
+}
+
+sub usage {
+ print <<"EOT";
+usage: $progname packagename
+ $progname --help
+ $progname --version
+
+Searches for all packages that build-depend on the specified package.
+
+Options:
+ -u, --update Run apt-get update before searching for build-depends.
+ (needs root privileges)
+ -s, --sudo Use sudo when running apt-get update
+ (has no effect when -u is omitted)
+ -q, --quiet Don't print meta information
+ -d, --debug Enable the debug mode
+ -m, --print-maintainer Print the maintainer information (experimental)
+ --distribution distribution Select a distribution to search for build-depends
+ --origin origin Select an origin to search for build-depends
+ (Default: Debian)
+ --only-main Ignore contrib, non-free and non-free-firmware
+ --only-devel Consider only development distributions
+ --exclude-component COMPONENT Ignore the specified component (can be given multiple times)
+ --host-arch Set the host architecture (requires dose-extra >= 4.0)
+ --build-arch Set the build architecture (requires dose-extra >= 4.0)
+ --no-arch-all Ignore Build-Depends-Indep
+ --no-arch-any Ignore Build-Depends-Arch
+ --old Use the old simple reverse dependency resolution
+
+EOT
+ version;
+}
+
+sub test_ceve {
+ return $ceve_compatible if defined $ceve_compatible;
+
+ # test if the debsrc input and output format is supported by the installed
+ # ceve version
+ system('dose-ceve -T debsrc debsrc:///dev/null > /dev/null 2>&1');
+ if ($? == -1) {
+ print STDERR "DEBUG: dose-ceve cannot be executed: $!\n"
+ if ($opt_debug);
+ $ceve_compatible = 0;
+ } elsif ($? == 0) {
+ $ceve_compatible = 1;
+ } else {
+ print STDERR "DEBUG: dose-ceve is too old\n" if ($opt_debug);
+ $ceve_compatible = 0;
+ }
+ return $ceve_compatible;
+}
+
+sub is_devel_release {
+ my $ctrl = shift;
+ if (get_current_vendor() eq 'Debian') {
+ return $ctrl->{Suite} eq 'unstable' || $ctrl->{Codename} eq 'sid';
+ } else {
+ return $ctrl->{Suite} eq 'devel';
+ }
+}
+
+sub indextargets {
+ my @cmd = ('apt-get', 'indextargets', 'DefaultEnabled: yes');
+
+ if (!$use_ceve) {
+ # ceve needs both Packages and Sources
+ push(@cmd, 'Created-By: Sources');
+ }
+
+ if ($opt_origin) {
+ push(@cmd, "Origin: $opt_origin");
+ }
+
+ if ($opt_mainonly) {
+ push(@cmd, 'Component: main');
+ }
+
+ print STDERR 'DEBUG: Running ' . join(' ', map { "'$_'" } @cmd) . "\n"
+ if $opt_debug;
+ return @cmd;
+}
+
+# Gather information about the available package/source lists.
+#
+# Returns a hash reference following this structure:
+#
+# <site> => {
+# <suite> => {
+# <component> => {
+# sources => $src_fname,
+# <arch1> => $arch1_fname,
+# ...,
+# },
+# },
+# ...,
+sub collect_files {
+ my %info = ();
+
+ open(my $targets, '-|', indextargets());
+
+ until (eof $targets) {
+ my $ctrl = Dpkg::Control->new(type => CTRL_UNKNOWN);
+ if (!$ctrl->parse($targets, 'apt-get indextargets')) {
+ next;
+ }
+ # Only need Sources/Packages stanzas
+ if ( $ctrl->{'Created-By'} ne 'Packages'
+ && $ctrl->{'Created-By'} ne 'Sources') {
+ next;
+ }
+
+ # In expected components
+ if ( !$opt_mainonly
+ && exists $ctrl->{Component}
+ && @opt_exclude_components) {
+ my $invalid_component = '(?:'
+ . join('|', map { "\Q$_\E" } @opt_exclude_components) . ')';
+ if ($ctrl->{Component} =~ m/$invalid_component/) {
+ next;
+ }
+ }
+
+ # And the provided distribution
+ if ($opt_distribution) {
+ if ( $ctrl->{Suite} !~ m/\Q$opt_distribution\E/
+ && $ctrl->{Codename} !~ m/\Q$opt_distribution\E/) {
+ next;
+ }
+ } elsif ($opt_develonly && !is_devel_release($ctrl)) {
+ next;
+ }
+
+ $info{ $ctrl->{Site} }{ $ctrl->{Suite} }{ $ctrl->{Component} } ||= {};
+ my $ref
+ = $info{ $ctrl->{Site} }{ $ctrl->{Suite} }{ $ctrl->{Component} };
+
+ if ($ctrl->{'Created-By'} eq 'Sources') {
+ $ref->{sources} = $ctrl->{Filename};
+ print STDERR "DEBUG: Added source file: $ctrl->{Filename}\n"
+ if $opt_debug;
+ } else {
+ $ref->{ $ctrl->{Architecture} } = $ctrl->{Filename};
+ }
+ }
+ close($targets);
+
+ return \%info;
+}
+
+sub findreversebuilddeps {
+ my ($package, $info) = @_;
+ my $count = 0;
+
+ my $source_file = $info->{sources};
+ if ($use_ceve) {
+ die "build arch undefined" if !defined $opt_buildarch;
+ die "host arch undefined" if !defined $opt_hostarch;
+
+ my $buildarch_file = $info->{$opt_buildarch};
+ my $hostarch_file = $info->{$opt_hostarch};
+
+ my @ceve_cmd = (
+ 'dose-ceve', '-T',
+ 'debsrc', '-r',
+ $package, '-G',
+ 'pkg', "--deb-native-arch=$opt_buildarch",
+ "deb://$buildarch_file", "debsrc://$source_file"
+ );
+ if ($opt_buildarch ne $opt_hostarch) {
+ push(@ceve_cmd,
+ "--deb-host-arch=$opt_hostarch",
+ "deb://$hostarch_file");
+ }
+ push(@ceve_cmd, "--deb-drop-b-d-indep") if ($opt_noarchall);
+ push(@ceve_cmd, "--deb-drop-b-d-arch") if ($opt_noarchany);
+ my %sources;
+ print STDERR 'DEBUG: executing: ' . join(' ', @ceve_cmd)
+ if ($opt_debug);
+ open(SOURCES, '-|', @ceve_cmd);
+ while (<SOURCES>) {
+ next unless s/^Package:\s+//;
+ chomp;
+ $sources{$_} = 1;
+ }
+ for my $source (sort keys %sources) {
+ print $source;
+ if ($opt_maintainer) {
+ my $maintainer
+ = `apt-cache showsrc $source | grep-dctrl -n -s Maintainer '' | sort -u`;
+ print " ($maintainer)";
+ }
+ print "\n";
+ $count += 1;
+ }
+ } else {
+ open(my $out, '-|', '/usr/lib/apt/apt-helper', 'cat-file',
+ $source_file)
+ or die
+"$progname: Unable to run \"apt-helper cat-file '$source_file'\": $!";
+
+ my %packages;
+ until (eof $out) {
+ my $ctrl = Dpkg::Control->new(type => CTRL_INDEX_SRC);
+ if (!$ctrl->parse($out, 'apt-helper cat-file')) {
+ next;
+ }
+ print STDERR "$ctrl\n" if ($opt_debug);
+ for my $relation (
+ qw(Build-Depends Build-Depends-Indep Build-Depends-Arch)) {
+ if (exists $ctrl->{$relation}) {
+ if ($ctrl->{$relation}
+ =~ m/^(.*\s)?\Q$package\E(?::[a-zA-Z0-9][a-zA-Z0-9-]*)?([\s,]|$)/
+ ) {
+ $packages{ $ctrl->{Package} }{Maintainer}
+ = $ctrl->{Maintainer};
+ }
+ }
+ }
+ }
+
+ close($out);
+
+ while (my $depending_package = each(%packages)) {
+ print $depending_package;
+ if ($opt_maintainer) {
+ print " ($packages{$depending_package}->{'Maintainer'})";
+ }
+ print "\n";
+ $count += 1;
+ }
+ }
+
+ if (!$opt_quiet) {
+ if ($count == 0) {
+ print "No reverse build-depends found for $package.\n\n";
+ } else {
+ print
+"\nFound a total of $count reverse build-depend(s) for $package.\n\n";
+ }
+ }
+}
+
+if ($#ARGV < 0) { usage; exit(0); }
+
+GetOptions(
+ "u|update" => \$opt_update,
+ "s|sudo" => \$opt_sudo,
+ "m|print-maintainer" => \$opt_maintainer,
+ "distribution=s" => \$opt_distribution,
+ "only-main" => \$opt_mainonly,
+ "only-devel" => \$opt_develonly,
+ "exclude-component=s" => \@opt_exclude_components,
+ "origin=s" => \$opt_origin,
+ "host-arch=s" => \$opt_hostarch,
+ "build-arch=s" => \$opt_buildarch,
+ "no-arch-all" => \$opt_noarchall,
+ "no-arch-any" => \$opt_noarchany,
+ # "profiles=s" => \$opt_profiles, # FIXME: add build profile support
+ # once dose-ceve has a
+ # --deb-profiles option
+ "old" => \$opt_without_ceve,
+ "q|quiet" => \$opt_quiet,
+ "d|debug" => \$opt_debug,
+ "h|help" => sub { usage; },
+ "v|version" => sub { version; }) or do { usage; exit 1; };
+
+my $package = shift;
+
+if (!$package) {
+ die "$progname: missing argument. expecting packagename\n";
+}
+
+print STDERR "DEBUG: Package => $package\n" if ($opt_debug);
+
+if ($opt_hostarch) {
+ if ($opt_without_ceve) {
+ die
+"$progname: the --host-arch option cannot be used together with --old\n";
+ }
+ if (test_ceve()) {
+ $use_ceve = 1;
+ } else {
+ die
+"$progname: the --host-arch option requires dose-extra >= 4.0 to be installed\n";
+ }
+}
+
+if ($opt_buildarch) {
+ if ($opt_without_ceve) {
+ die
+"$progname: the --build-arch option cannot be used together with --old\n";
+ }
+ if (test_ceve()) {
+ $use_ceve = 1;
+ } else {
+ die
+"$progname: the --build-arch option requires dose-extra >= 4.0 to be installed\n";
+ }
+}
+
+# if ceve usage has not been activated yet, check if it can be activated
+if (!$use_ceve and !$opt_without_ceve) {
+ if (test_ceve()) {
+ $use_ceve = 1;
+ } else {
+ print STDERR
+"WARNING: dose-extra >= 4.0 is not installed. Falling back to old unreliable behaviour.\n";
+ }
+}
+
+if ($use_ceve) {
+ if (system('command -v grep-dctrl >/dev/null 2>&1')) {
+ die
+"$progname: Fatal error. grep-dctrl is not available.\nPlease install the 'dctrl-tools' package.\n";
+ }
+
+ # set hostarch and buildarch if they have not been set yet
+ if (!$opt_hostarch) {
+ $opt_hostarch = `dpkg-architecture --query DEB_HOST_ARCH`;
+ chomp $opt_hostarch;
+ }
+ if (!$opt_buildarch) {
+ $opt_buildarch = `dpkg-architecture --query DEB_BUILD_ARCH`;
+ chomp $opt_buildarch;
+ }
+ print STDERR "DEBUG: running with dose-ceve resolver\n" if ($opt_debug);
+ print STDERR "DEBUG: buildarch=$opt_buildarch hostarch=$opt_hostarch\n"
+ if ($opt_debug);
+} else {
+ print STDERR "DEBUG: running with old resolver\n" if ($opt_debug);
+}
+
+if ($opt_update) {
+ print STDERR "DEBUG: Updating apt-cache before search\n" if ($opt_debug);
+ my @cmd;
+ if ($opt_sudo) {
+ print STDERR "DEBUG: Using sudo to become root\n" if ($opt_debug);
+ push(@cmd, 'sudo');
+ }
+ push(@cmd, 'apt-get', 'update');
+ system @cmd;
+}
+
+my $file_info = collect_files();
+
+if (!%{$file_info}) {
+ die
+"$progname: unable to find sources files.\nDid you forget to run apt-get update (or add --update to this command)?";
+}
+
+foreach my $site (sort keys %{$file_info}) {
+ foreach my $suite (sort keys %{ $file_info->{$site} }) {
+ foreach my $comp (qw(main contrib non-free non-free-firmware)) {
+ if (exists $file_info->{$site}{$suite}{$comp}) {
+ if (!$opt_quiet) {
+ my $msg = "Reverse Build-depends in $suite/$comp:";
+ print "$msg\n";
+ print "-" x length($msg) . "\n\n";
+ }
+ findreversebuilddeps($package,
+ $file_info->{$site}{$suite}{$comp});
+ }
+ }
+ }
+}
+
+=head1 LICENSE
+
+This code is copyright by Patrick Schoenfeld
+<schoenfeld@debian.org>, all rights reserved.
+This program comes with ABSOLUTELEY NO WARRANTY.
+You are free to redistribute this code under the terms of the
+GNU General Public License, version 2 or later.
+
+=head1 AUTHOR
+
+Patrick Schoenfeld <schoenfeld@debian.org>
+
+=cut