#!/usr/bin/perl # # wanna-build: coordination script for Debian buildds # Copyright (C) 1998 Roman Hodek # Copyright (C) 2005-2008 Ryan Murray # Copyright (C) 2010 Andreas Barth # # 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 St, Fifth Floor, Boston, MA 02110-1301 USA # use strict; use warnings; use 5.010; package conf; use vars qw< $basedir $dbbase $transactlog $mailprog $buildd_domain >; # defaults $basedir ||= "/var/lib/debbuild"; $dbbase ||= "build-db"; $transactlog ||= "transactions.log"; $mailprog ||= "/usr/sbin/sendmail"; require "/org/wanna-build/etc/wanna-build.conf"; die "$conf::basedir is not a directory\n" if ! -d $conf::basedir; die "dbbase is empty\n" if ! $dbbase; die "transactlog is empty\n" if ! $transactlog; die "mailprog binary $conf::mailprog does not exist or isn't executable\n" if !-x $conf::mailprog; package main; use POSIX; use FileHandle; use File::Copy; use DBI; use Getopt::Long qw ( :config gnu_getopt ); use lib '/org/wanna-build/lib'; #use lib 'lib'; use WannaBuild; use YAML::Tiny; use Data::Dumper; use Hash::Merge qw ( merge ); use String::Format; use Date::Parse; use List::Util qw[max]; use Dpkg::Version (); # import nothing if ( defined $Dpkg::Version::VERSION ) { *vercmp = \&Dpkg::Version::version_compare; } else { *vercmp = \&Dpkg::Version::vercmp; } use Dpkg::Deps; # TODO: same our ($verbose, $mail_logs, $list_order, $list_state, $curr_date, $op_mode, $user, $real_user, $distribution, $fail_reason, $opt_override, $import_from, $export_to, %prioval, %sectval, $info_all_dists, $arch, $short_date, $list_min_age, $list_max_age, $dbbase, @curr_time, $build_priority, %new_vers, $binNMUver, %merge_srcvers, %merge_binsrc, $printformat, $ownprintformat, $privmode, $extra_depends, $extra_conflicts, %distributions, %distribution_aliases ); our $Pas = '/org/buildd.debian.org/etc/packages-arch-specific/Packages-arch-specific'; our $simulate = 0; our $simulate_edos = 0; our $api = undef; # allow buildds to specify an different api our $recorduser = undef; # global vars $ENV{'PATH'} = "/bin:/usr/bin:/usr/local/bin:/org/wanna-build/bin/"; $ENV{'LC_ALL'} = 'C'; $verbose = 0; $mail_logs = ""; @curr_time = gmtime; $curr_date = strftime("%Y %b %d %H:%M:%S",@curr_time); $short_date = strftime("%m/%d/%y",@curr_time); $| = 1; # set mode of operation based on command line switch. Should be used # by GetOptions below. sub _set_mode_set { $op_mode = "set-$_[0]" } sub _set_mode { $op_mode = "$_[0]" } sub _option_deprecated { warn "Option $_[0] is deprecated" } GetOptions( # this is not supported by all operations (yet)! 'simulate' => \$simulate, 'simulate-edos' => \$simulate_edos, 'simulate-all' => sub { $simulate = 1; $simulate_edos = 1; }, 'api=i' => sub { $api = $_[1]; die "$api too large" unless $api <= 1; }, 'verbose|v' => \$verbose, 'override|o' => \$opt_override, 'correct-compare' => \$WannaBuild::opt_correct_version_cmp, # TODO: remove after buildds no longer pass to wanna-build 'no-propagation|N' => \&_option_deprecated, 'no-down-propagation|D' => \&_option_deprecated, # normal actions 'building|take' => \&_set_mode_set, 'failed|f' => \&_set_mode_set, 'uploaded|u' => \&_set_mode_set, 'not-for-us|no-build|n' => \&_set_mode_set, 'built' => \&_set_mode_set, 'attempted' => \&_set_mode_set, 'needs-build|give-back' => \&_set_mode_set, 'dep-wait' => \&_set_mode_set, 'update' => \&_set_mode_set, 'forget' => \&_set_mode, 'forget-user' => \&_set_mode, 'merge-v3' => \&_set_mode, 'info|i' => \&_set_mode, 'binary-nmu|binNMU=i' => sub { _set_mode_set(@_); $binNMUver = $_[1]; }, 'permanent-build-priority|perm-build-priority=i' => sub { _set_mode_set(@_); $build_priority = $_[1]; }, 'build-priority=i' => sub { _set_mode_set(@_); $build_priority = $_[1]; }, 'list|l=s' => sub { _set_mode(@_); $list_state = $_[1]; die "Unknown state to list: $list_state\n" if not $list_state ~~ [ qw( needs-build building uploaded built build-attempted failed installed dep-wait not-for-us auto-not-for-us all failed-removed install-wait reupload-wait bd-uninstallable ) ]; }, 'dist|d=s' => sub { given ( $_[1] ) { when ( [qw< a all >] ) { $info_all_dists = 1; $distribution = ''; } when ('o') { $distribution = 'oldstable'; } when ('s') { $distribution = 'stable'; } when ('t') { $distribution = 'testing'; } when ('u') { $distribution = 'unstable'; } } }, 'order|O=s' => sub { $list_order = $_[1]; die "Bad ordering character\n" if $list_order !~ /^[PSpsncbCWT]+$/; }, 'message|m=s' => \$fail_reason, 'database|b=s' => sub { warn "database is deprecated, please use 'arch' instead.\n"; $conf::dbbase = $_[1]; }, 'arch|A=s' => \$arch, 'user|U=s' => \$user, 'min-age|a=i' => \$list_min_age, 'max-age=i' => \$list_max_age, 'format=s' => \$printformat, 'own-format=s' => \$ownprintformat, 'Pas=s' => \$Pas, 'extra-depends=s' => \$extra_depends, 'extra-conflicts=s' => \$extra_conflicts, # special actions 'export' => sub { _set_mode(@_); $export_to = $_[1]; }, 'import' => sub { _set_mode(@_); $import_from = $_[1]; }, 'manual-edit' => \&_set_mode, 'distribution-architectures' => \&_set_mode, 'distribution-aliases' => \&_set_mode, ) or usage(); $list_min_age = -1 * $list_max_age if $list_max_age; my $dbh; END { if (defined $dbh) { $dbh->disconnect or warn $dbh->errstr; } } $distribution ||= "sid"; if ($distribution eq 'any-priv') { $privmode = 1; $distribution = 'any'; } if ($distribution eq 'any-unpriv') { $privmode = 0; $distribution = 'any'; } my $schema_suffix = ''; $recorduser //= (not -t and $user//"" =~ /^buildd_/); if ((isin( $op_mode, qw(list info)) && $distribution !~ /security/ && !$recorduser && !($privmode)) || $simulate) { $dbh = DBI->connect("DBI:Pg:service=wanna-build") || die "FATAL: Cannot open database: $DBI::errstr\n"; $schema_suffix = '_public'; } else { $dbh = DBI->connect("DBI:Pg:service=wanna-build-privileged") || die "FATAL: Cannot open database: $DBI::errstr\n"; } # TODO: This shouldn't be needed, file a bug. $dbh->{pg_server_prepare} = 0; $dbh->begin_work or die $dbh->errstr; my $q = 'SELECT distribution, public, auto_dep_wait, build_dep_resolver, suppress_successful_logs, archive FROM distributions'; my $rows = $dbh->selectall_hashref($q, 'distribution'); foreach my $name (keys %$rows) { $distributions{$name} = {}; $distributions{$name}->{'noadw'} = 1 if !($rows->{$name}->{'auto_dep_wait'}); $distributions{$name}->{'hidden'} = 1 if !($rows->{$name}->{'public'}); $distributions{$name}->{'build_dep_resolver'} = $rows->{$name}->{'build_dep_resolver'} if $rows->{$name}->{'build_dep_resolver'}; $distributions{$name}->{'suppress_successful_logs'} = $rows->{$name}->{'suppress_successful_logs'} if $rows->{$name}->{'suppress_successful_logs'}; $distributions{$name}->{'archive'} = $rows->{$name}->{'archive'} if $rows->{$name}->{'archive'}; } $q = 'SELECT alias, distribution FROM distribution_aliases'; $rows = $dbh->selectall_hashref($q, 'alias'); foreach my $name (keys %$rows) { $distribution_aliases{$name} = $rows->{$name}->{'distribution'}; } $distribution = $distribution_aliases{$distribution} if (isin($distribution, keys %distribution_aliases)); $op_mode ||= "set-building"; undef $distribution if $distribution eq 'any'; if ($distribution) { my @dists = split(/[, ]+/, $distribution); foreach my $dist (@dists) { die "Bad distribution '$distribution'\n" if !isin($dist, keys %distributions); } } if (!isin ( $op_mode, qw(list) ) && ( !$distribution || $distribution =~ /[ ,]/)) { die "multiple distributions are only allowed for list"; } # If they didn't specify an arch, try to get it from database name which # is in the form of $arch/build-db # This is for backwards compatibity with older versions that didn't # specify the arch yet. $conf::dbbase =~ m#^([^/]+)#; $arch ||= $1; # TODO: Check that it's an known arch (for that dist), and give # a proper error. if ($verbose) { my $version = '$Revision: db181a534e9d $ $Date: 2008/03/26 06:20:22 $ $Author: rmurray $'; $version =~ s/(^\$| \$ .*$)//g; print "wanna-build $version for $distribution on $arch\n"; } if (!@ARGV && !isin( $op_mode, qw(list merge-quinn merge-partial-quinn import export merge-packages manual-edit merge-sources distribution-architectures distribution-aliases))) { warn "No packages given.\n"; usage(); } $real_user = (getpwuid($<))[0]; die "Can't determine your user name\n" if $op_mode ne "list" && !$user && !($user = $real_user); if (!$fail_reason) { if ($op_mode eq "set-failed" ) { print "Enter reason for failing (end with '.' alone on ". "its line):\n"; my $line; while(!eof(STDIN)) { $line = ; last if $line eq ".\n"; $fail_reason .= $line; } chomp( $fail_reason ); } elsif ($op_mode eq "set-dep-wait") { print "Enter dependencies (one line):\n"; my $line; while( !$line && !eof(STDIN) ) { chomp( $line = ); } die "No dependencies given\n" if !$line; $fail_reason = $line; } elsif ($op_mode eq "set-binary-nmu" and $binNMUver > 0) { print "Enter changelog entry (one line):\n"; my $line; while( !$line && !eof(STDIN) ) { chomp( $line = ); } die "No changelog entry given\n" if !$line; $fail_reason = $line; } } my $yamlmap = (); my $yamldir = "/org/wanna-build/etc/yaml"; my @files = ('wanna-build.yaml'); if ((getpwuid($>))[7]) { push (@files, ((getpwuid($>))[7])."/.wanna-build.yaml"); } if ($user && $user =~ /(buildd.*)-/) { push (@files, "$1.yaml") }; if ($user) { push ( @files, "$user.yaml"); } foreach my $file (@files) { my $cfile = File::Spec->rel2abs( $file, $yamldir ); if ($verbose >= 2) { print "Trying to read $file ($cfile) ...\n"; } next unless -f $cfile; if ($verbose >= 2) { print "Read $file ($cfile) ...\n"; } my $m = YAML::Tiny->read( $cfile )->[0]; $yamlmap = merge($m, $yamlmap); } if (not $yamlmap) { die "FATAL: no configuration found\n"; } $list_order = $yamlmap->{"list-order"}{$list_state} if !$list_order and $list_state; $list_order ||= $yamlmap->{"list-order"}{'default'}; $api //= $yamlmap->{"api"}; $api //= 0; process(); $dbh->commit; $dbh->disconnect; if ($mail_logs && $conf::log_mail) { send_mail( $conf::log_mail, "wanna-build $distribution state changes $curr_date", "State changes at $curr_date for distribution ". "$distribution:\n\n$mail_logs\n" ); } exit 0; sub process { SWITCH: foreach ($op_mode) { /^set-(.+)/ && do { add_packages( $1, @ARGV ); last SWITCH; }; /^list/ && do { list_packages( $list_state ); last SWITCH; }; /^info/ && do { info_packages( @ARGV ); last SWITCH; }; /^forget-user/ && do { die "This operation is restricted to admin users\n" if (defined @conf::admin_users and !isin( $real_user, @conf::admin_users)); forget_users( @ARGV ); last SWITCH; }; /^forget/ && do { forget_packages( @ARGV ); last SWITCH; }; /^merge-v3/ && do { die "This operation is restricted to admin users\n" if (defined @conf::admin_users and !isin( $real_user, @conf::admin_users) and !$simulate); # call with installed-packages+ . installed-sources+ [ . available-for-build-packages* [ . consider-as-installed-source* ] ] # in case available-for-build-packages is not specified, installed-packages are used lock_table() unless $simulate; my $replacemap = { '%ARCH%' => $arch, '%SUITE%' => $distribution }; map { my $k = $_; grep { $k =~ s,$_,$replacemap->{$_}, } keys %{$replacemap}; $_ = $k; } @ARGV; my @ipkgs = &parse_argv( \@ARGV, '.'); my @isrcs = &parse_argv( \@ARGV, '.'); my @bpkgs = &parse_argv( \@ARGV, '.'); my @psrcs = &parse_argv( \@ARGV, '.'); use WB::QD; my $srcs = WB::QD::readsourcebins($arch, $Pas, \@isrcs, \@ipkgs); if (@psrcs) { my $psrcs = WB::QD::readsourcebins($arch, $Pas, \@psrcs, []); foreach my $k (keys %$$psrcs) { next if $$srcs->{$k}; my $pkg = $$psrcs->{$k}; $pkg->{'status'} = 'related'; $$srcs->{$k} = $pkg; } } parse_all_v3($$srcs, {'arch' => $arch, 'suite' => $distribution, 'time' => $curr_date}); @bpkgs = @ipkgs unless @bpkgs; call_edos_depcheck( {'arch' => $arch, 'pkgs' => \@bpkgs, 'srcs' => $$srcs, 'depwait' => 1 }); last SWITCH; }; /^import/ && do { die "This operation is restricted to admin users\n" if (defined @conf::admin_users and !isin( $real_user, @conf::admin_users)); $dbh->do("DELETE from " . table_name() . " WHERE distribution = ?", undef, $distribution) or die $dbh->errstr; forget_users(); read_db( $import_from ); last SWITCH; }; /^export/ && do { export_db( $export_to ); last SWITCH; }; /^distribution-architectures/ && do { show_distribution_architectures(); last SWITCH; }; /^distribution-aliases/ && do { show_distribution_aliases(); last SWITCH; }; die "Unexpected operation mode $op_mode\n"; } if ($recorduser) { my $userinfo = get_user_info($user); if (!defined $userinfo) { add_user_info($user); } else { update_user_info($user); } } } sub add_packages { my $newstate = shift; my( $package, $name, $version, $ok, $reason ); foreach $package (@_) { $package =~ s,^.*/,,; # strip path $package =~ s/\.(dsc|diff\.gz|tar\.gz|deb)$//; # strip extension $package =~ s/_[a-zA-Z\d-]+\.changes$//; # strip extension if ($package =~ /^([\w\d.+-]+)_([\w\d:.+~-]+)/) { ($name,$version) = ($1,$2); } else { warn "$package: can't extract package name and version ". "(bad format)\n"; next; } if ($op_mode eq "set-building") { add_one_building( $name, $version ); } elsif ($op_mode eq "set-built") { add_one_built( $name, $version ); } elsif ($op_mode eq "set-attempted") { add_one_attempted( $name, $version ); } elsif ($op_mode eq "set-uploaded") { add_one_uploaded( $name, $version ); } elsif ($op_mode eq "set-failed") { add_one_failed( $name, $version ); } elsif ($op_mode eq "set-not-for-us") { add_one_notforus( $name, $version ); } elsif ($op_mode eq "set-needs-build") { add_one_needsbuild( $name, $version ); } elsif ($op_mode eq "set-dep-wait") { add_one_depwait( $name, $version ); } elsif ($op_mode eq "set-build-priority") { set_one_buildpri( $name, $version, 'buildpri' ); } elsif ($op_mode eq "set-permanent-build-priority") { set_one_buildpri( $name, $version, 'permbuildpri' ); } elsif ($op_mode eq "set-binary-nmu") { set_one_binnmu( $name, $version ); } elsif ($op_mode eq "set-update") { set_one_update( $name, $version ); } } } sub add_one_building { my $name = shift; my $version = shift; my( $ok, $reason ); $ok = 1; my $pkg = get_source_info($name); if (defined($pkg)) { if ($pkg->{'state'} eq "Not-For-Us") { $ok = 0; $reason = "not suitable for this architecture"; } elsif ($pkg->{'state'} =~ /^Dep-Wait/) { $ok = 0; $reason = "not all source dependencies available yet"; } elsif ($pkg->{'state'} =~ /^BD-Uninstallable/) { $ok = 0; $reason = "source dependencies are not installable"; } elsif ($pkg->{'state'} eq "Uploaded" && (version_lesseq($version, $pkg->{'version'}))) { $ok = 0; $reason = "already uploaded by $pkg->{'builder'}"; $reason .= " (in newer version $pkg->{'version'})" if !version_eq($pkg, $version); } elsif ($pkg->{'state'} eq "Installed" && version_less($version,$pkg->{'version'})) { if ($opt_override) { print "$name: Warning: newer version $pkg->{'version'} ". "already installed, but overridden.\n"; } else { $ok = 0; $reason = "newer version $pkg->{'version'} already in ". "archive; doesn't need rebuilding"; print "$name: Note: If the following is due to an epoch ", " change, use --override\n"; } } elsif ($pkg->{'state'} eq "Installed" && pkg_version_eq($pkg,$version)) { $ok = 0; $reason = "is up-to-date in the archive; doesn't need rebuilding"; } elsif ($pkg->{'state'} eq "Needs-Build" && version_less($version,$pkg->{'version'})) { if ($opt_override) { print "$name: Warning: newer version $pkg->{'version'} ". "needs building, but overridden."; } else { $ok = 0; $reason = "newer version $pkg->{'version'} needs building, ". "not $version"; } } elsif (isin($pkg->{'state'},qw(Building Built Build-Attempted))) { if (version_less($pkg->{'version'},$version)) { print "$name: Warning: Older version $pkg->{'version'} ", "is being built by $pkg->{'builder'}\n"; if ($pkg->{'builder'} ne $user) { send_mail( $pkg->{'builder'}, "package takeover in newer version", "You are building package '$name' in ". "version $version\n". "(as far as I'm informed).\n". "$user now has taken the newer ". "version $version for building.". "You can abort the build if you like.\n" ); } } else { if ($opt_override) { print "User $pkg->{'builder'} had already ", "taken the following package,\n", "but overriding this as you request:\n"; send_mail( $pkg->{'builder'}, "package takeover", "The package '$name' (version $version) that ". "was taken by you\n". "has been taken over by $user\n" ); } elsif ($pkg->{'builder'} eq $user) { print "$name: Note: already taken by you.\n"; print "$name: ok\n" if $verbose; return; } else { $ok = 0; $reason = "already taken by $pkg->{'builder'}"; $reason .= " (in newer version $pkg->{'version'})" if !version_eq($pkg->{'version'}, $version); } } } elsif ($pkg->{'state'} =~ /^Failed/ && pkg_version_eq($pkg, $version)) { if ($opt_override) { print "The following package previously failed ", "(by $pkg->{'builder'})\n", "but overriding this as you request:\n"; send_mail( $pkg->{'builder'}, "failed package takeover", "The package '$name' (version $version) that ". "is taken by you\n". "and has failed previously has been taken over ". "by $user\n" ) if $pkg->{'builder'} ne $user; } else { $ok = 0; $reason = "build of $version failed previously:\n "; $reason .= join( "\n ", split( "\n", $pkg->{'failed'} )); $reason .= "\nalso the package doesn't need builing" if $pkg->{'state'} eq 'Failed-Removed'; } } } if ($ok) { if ($api < 1) { my $ok = 'ok'; if ($pkg->{'binary_nmu_version'}) { print "$name: Warning: needs binary NMU $pkg->{'binary_nmu_version'}\n" . "$pkg->{'binary_nmu_changelog'}\n"; $ok = 'aok'; } else { print "$name: Warning: Previous version failed!\n" if $pkg->{'previous_state'} =~ /^Failed/ || $pkg->{'state'} =~ /^Failed/; } print "$name: $ok\n" if $verbose; } else { print "- $name:\n"; print " - status: ok\n"; printf " - pkg-ver: %s_%s\n", $name, $version; print " - binNMU: $pkg->{'binary_nmu_version'}\n" if $pkg->{'binary_nmu_version'}; print " - extra-changelog: $pkg->{'binary_nmu_changelog'}\n" if $pkg->{'binary_nmu_changelog'} && $pkg->{'binary_nmu_version'}; print " - extra-depends: $pkg->{'extra_depends'}\n" if $pkg->{'extra_depends'}; print " - extra-conflicts: $pkg->{'extra_conflicts'}\n" if $pkg->{'extra_conflicts'}; print " - archive: $distributions{$distribution}->{'archive'}\n" if $distributions{$distribution}->{'archive'}; print " - build_dep_resolver: $distributions{$distribution}->{'build_dep_resolver'}\n" if $distributions{$distribution}->{'build_dep_resolver'}; print " - arch_all: $pkg->{'build_arch_all'}\n" if $pkg->{'build_arch_all'}; print " - suppress_successful_logs: $distributions{$distribution}->{'suppress_successful_logs'}\n" if $distributions{$distribution}->{'suppress_successful_logs'}; } change_state( \$pkg, 'Building' ); $pkg->{'package'} = $name; $pkg->{'version'} = $version; $pkg->{'builder'} = $user; log_ta( $pkg, "--take" ); update_source_info($pkg); } else { if ($api < 1) { print "$name: NOT OK!\n $reason\n"; } else { print "- $name:\n - status: not ok\n - reason: \"$reason\"\n"; } } } sub add_one_attempted { my $name = shift; my $version = shift; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } if (($pkg->{'state'} ne "Building") && ($pkg->{'state'} ne "Build-Attempted")) { print "$name: not taken for building (state is $pkg->{'state'}). ", "Skipping.\n"; return; } if ($pkg->{'builder'} ne $user) { print "$name: not taken by you, but by $pkg->{'builder'}. Skipping.\n"; return; } elsif ( !pkg_version_eq($pkg, $version) ) { print "$name: version mismatch ". "$(pkg->{'version'} ". "by $pkg->{'builder'})\n"; return; } change_state( \$pkg, 'Build-Attempted' ); log_ta( $pkg, "--attempted" ); update_source_info($pkg); print "$name: registered as uploaded\n" if $verbose; } sub add_one_built { my $name = shift; my $version = shift; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } if (($pkg->{'state'} ne "Building") && ($pkg->{'state'} ne "Build-Attempted")) { print "$name: not taken for building (state is $pkg->{'state'}). ", "Skipping.\n"; return; } if ($pkg->{'builder'} ne $user) { print "$name: not taken by you, but by $pkg->{'builder'}. Skipping.\n"; return; } elsif ( !pkg_version_eq($pkg, $version) ) { print "$name: version mismatch ". "$(pkg->{'version'} ". "by $pkg->{'builder'})\n"; return; } change_state( \$pkg, 'Built' ); log_ta( $pkg, "--built" ); update_source_info($pkg); print "$name: registered as built\n" if $verbose; } sub add_one_uploaded { my $name = shift; my $version = shift; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } if ($pkg->{'state'} eq "Uploaded" && pkg_version_eq($pkg,$version)) { print "$name: already uploaded\n"; return; } if (!isin( $pkg->{'state'}, qw(Building Built Build-Attempted))) { print "$name: not taken for building (state is $pkg->{'state'}). ", "Skipping.\n"; return; } if ($pkg->{'builder'} ne $user) { print "$name: not taken by you, but by $pkg->{'builder'}. Skipping.\n"; return; } # strip epoch -- buildd-uploader used to go based on the filename. # (to remove at some point) my $pkgver; ($pkgver = $pkg->{'version'}) =~ s/^\d+://; $version =~ s/^\d+://; # for command line use if ($pkg->{'binary_nmu_version'} ) { my $nmuver = binNMU_version($pkgver, $pkg->{'binary_nmu_version'}); if (!version_eq( $nmuver, $version )) { print "$name: version mismatch ($nmuver registered). ", "Skipping.\n"; return; } } elsif (!version_eq($pkgver, $version)) { print "$name: version mismatch ($pkg->{'version'} registered). ", "Skipping.\n"; return; } change_state( \$pkg, 'Uploaded' ); log_ta( $pkg, "--uploaded" ); update_source_info($pkg); print "$name: registered as uploaded\n" if $verbose; } sub add_one_failed { my $name = shift; my $version = shift; my $state; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } $state = $pkg->{'state'}; if ($state eq "Not-For-Us") { print "$name: not suitable for this architecture anyway. Skipping.\n"; return; } elsif ($state eq "Failed-Removed") { print "$name: failed previously and doesn't need building. Skipping.\n"; return; } elsif ($state eq "Installed") { print "$name: Is already installed in archive. Skipping.\n"; return; } elsif ($pkg->{'builder'} && (($user ne $pkg->{'builder'}) && !($pkg->{'builder'} =~ /^(\w+)-\w+/ && $1 eq $user))) { print "$name: not taken by you, but by ". "$pkg->{'builder'}. Skipping.\n"; return; } elsif ( !pkg_version_eq($pkg, $version) ) { print "$name: version mismatch ". "$(pkg->{'version'} ". "by $pkg->{'builder'})\n"; return; } if ($state eq "Needs-Build") { print "$name: Warning: not registered for building previously, ". "but processing anyway.\n"; } elsif ($state eq "Uploaded") { print "$name: Warning: marked as uploaded previously, ". "but processing anyway.\n"; } elsif ($state eq "Dep-Wait") { print "$name: Warning: marked as waiting for dependencies, ". "but processing anyway.\n"; } elsif ($state eq "BD-Uninstallable") { print "$name: Warning: marked as having uninstallable build-dependencies, ". "but processing anyway.\n"; } elsif ($state eq "Failed") { print "$name: already registered as failed; will append new message\n" if $fail_reason; } change_state( \$pkg, 'Failed' ); $pkg->{'builder'} = $user; $pkg->{'failed'} .= "\n" if $pkg->{'failed'}; $pkg->{'failed'} .= $fail_reason; if (defined $pkg->{'permbuildpri'}) { $pkg->{'buildpri'} = $pkg->{'permbuildpri'}; } else { delete $pkg->{'buildpri'}; } log_ta( $pkg, "--failed" ); update_source_info($pkg); print "$name: registered as failed\n" if $verbose; } sub add_one_notforus { my $name = shift; my $version = shift; my $pkg = get_source_info($name); if ($pkg->{'state'} eq 'Not-For-Us') { # reset Not-For-Us state in case it's called twice; this is # the only way to get a package out of this state... # There is no really good state in which such packages should # be put :-( So use Failed for now. change_state( \$pkg, 'Failed' ); $pkg->{'package'} = $name; $pkg->{'failed'} = "Was Not-For-Us previously"; delete $pkg->{'builder'}; delete $pkg->{'depends'}; log_ta( $pkg, "--no-build(rev)" ); print "$name: now not unsuitable anymore\n"; send_mail( $conf::notforus_maint, "$name moved out of Not-For-Us state", "The package '$name' has been moved out of the Not-For-Us ". "state by $user.\n". "It should probably also be removed from ". "Packages-arch-specific or\n". "the action was wrong.\n" ) if $conf::notforus_maint; } else { change_state( \$pkg, 'Not-For-Us' ); $pkg->{'package'} = $name; delete $pkg->{'builder'}; delete $pkg->{'depends'}; delete $pkg->{'buildpri'}; delete $pkg->{'binary_nmu_version'}; delete $pkg->{'binary_nmu_changelog'}; log_ta( $pkg, "--no-build" ); print "$name: registered as unsuitable\n" if $verbose; send_mail( $conf::notforus_maint, "$name set to Not-For-Us", "The package '$name' has been set to state Not-For-Us ". "by $user.\n". "It should probably also be added to ". "Packages-arch-specific or\n". "the Not-For-Us state is wrong.\n" ) if $conf::notforus_maint; } update_source_info($pkg); } sub add_one_needsbuild { my $name = shift; my $version = shift; my $state; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered; can't give back.\n"; return; } $state = $pkg->{'state'}; if ($state eq "BD-Uninstallable") { if ($opt_override) { print "$name: Forcing uninstallability mark to be removed. This is not permanent and might be reset with the next trigger run\n"; change_state( \$pkg, 'Needs-Build' ); delete $pkg->{'builder'}; delete $pkg->{'depends'}; log_ta( $pkg, "--give-back" ); update_source_info($pkg); print "$name: given back\n" if $verbose; return; } else { print "$name: has uninstallable build-dependencies. Skipping\n", " (use --override to clear dependency list and ", "give back anyway)\n"; return; } } elsif ($state eq "Dep-Wait") { if ($opt_override) { print "$name: Forcing source dependency list to be cleared\n"; } else { print "$name: waiting for source dependencies. Skipping\n", " (use --override to clear dependency list and ", "give back anyway)\n"; return; } } elsif (!isin( $state, qw(Building Built Build-Attempted))) { print "$name: not taken for building (state is $state)."; if ($opt_override) { print "\n$name: Forcing give-back\n"; } else { print " Skipping.\n"; return; } } if (defined ($pkg->{'builder'}) && $user ne $pkg->{'builder'} && !($pkg->{'builder'} =~ /^(\w+)-\w+/ && $1 eq $user) && !$opt_override) { print "$name: not taken by you, but by ". "$pkg->{'builder'}. Skipping.\n"; return; } if (!pkg_version_eq($pkg, $version)) { print "$name: version mismatch ($pkg->{'version'} registered). ", "Skipping.\n"; return; } if (!defined $distributions{$distribution}{noadw}) { change_state( \$pkg, 'BD-Uninstallable' ); $pkg->{'bd_problem'} = "Installability of build dependencies not tested yet"; } else { change_state( \$pkg, 'Needs-Build' ); } $pkg->{'builder'} = undef; $pkg->{'depends'} = undef; log_ta( $pkg, "--give-back" ); update_source_info($pkg); print "$name: given back\n" if $verbose; } sub set_one_binnmu { my $name = shift; my $version = shift; my $pkg = get_source_info($name); my $state; if (!defined($pkg)) { print "$name: not registered; can't register for binNMU.\n"; return; } my $db_ver = $pkg->{'version'}; if (!version_eq($db_ver, $version)) { print "$name: version mismatch ($db_ver registered). ", "Skipping.\n"; return; } $state = $pkg->{'state'}; if (defined $pkg->{'binary_nmu_version'}) { if ($binNMUver == 0) { change_state( \$pkg, 'Installed' ); delete $pkg->{'builder'}; delete $pkg->{'depends'}; delete $pkg->{'binary_nmu_version'}; delete $pkg->{'binary_nmu_changelog'}; } elsif ($binNMUver <= $pkg->{'binary_nmu_version'}) { print "$name: already building binNMU $pkg->{'binary_nmu_version'}\n"; return; } else { $pkg->{'binary_nmu_version'} = $binNMUver; $pkg->{'binary_nmu_changelog'} = $fail_reason; $pkg->{'notes'} = 'out-of-date'; $pkg->{'buildpri'} = $pkg->{'permbuildpri'} if (defined $pkg->{'permbuildpri'}); if (defined $distributions{$distribution}{noadw}) { change_state( \$pkg, 'Installed' ); } else { change_state( \$pkg, 'BD-Uninstallable' ); } } log_ta( $pkg, "--binNMU" ); update_source_info($pkg); return; } elsif ($binNMUver == 0) { print "${name}_$version: no scheduled binNMU to cancel.\n"; return; } if ($state ne 'Installed') { print "${name}_$version: not installed; can't register for binNMU.\n"; return; } my $fullver = binNMU_version($version,$binNMUver); if ( version_lesseq( $fullver, $pkg->{'installed_version'} ) ) { print "$name: binNMU $fullver is not newer than current version $pkg->{'installed_version'}\n"; return; } if (!defined $distributions{$distribution}{noadw}) { change_state( \$pkg, 'BD-Uninstallable' ); $pkg->{'bd_problem'} = "Installability of build dependencies not tested yet"; } else { change_state( \$pkg, 'Needs-Build' ); } delete $pkg->{'builder'}; delete $pkg->{'depends'}; $pkg->{'binary_nmu_version'} = $binNMUver; $pkg->{'binary_nmu_changelog'} = $fail_reason; $pkg->{'notes'} = 'out-of-date'; log_ta( $pkg, "--binNMU" ); update_source_info($pkg); print "${name}: registered for binNMU $fullver\n" if $verbose; } sub set_one_buildpri { my $name = shift; my $version = shift; my $key = shift; my $pkg = get_source_info($name); my $state; if (!defined($pkg)) { print "$name: not registered; can't set priority.\n"; return; } $state = $pkg->{'state'}; if ($state eq "Not-For-Us") { print "$name: not suitable for this architecture. Skipping.\n"; return; } elsif ($state eq "Failed-Removed") { print "$name: failed previously and doesn't need building. Skipping.\n"; return; } if (!pkg_version_eq($pkg, $version)) { print "$name: version mismatch ($pkg->{'version'} registered). ", "Skipping.\n"; return; } if ( $build_priority == 0 ) { delete $pkg->{'buildpri'} if $key eq 'permbuildpri' and defined $pkg->{'buildpri'} and $pkg->{'buildpri'} == $pkg->{$key}; delete $pkg->{$key}; } else { $pkg->{'buildpri'} = $build_priority if $key eq 'permbuildpri'; $pkg->{$key} = $build_priority; } update_source_info($pkg); print "$name: set to build priority $build_priority\n" if $verbose; } sub add_one_depwait { my $name = shift; my $version = shift; my $state; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } $state = $pkg->{'state'}; if ($state eq "Dep-Wait") { print "$name: merging with previously registered dependencies\n"; } if (isin( $state, qw(Needs-Build Failed BD-Uninstallable))) { print "$name: Warning: not registered for building previously, ". "but processing anyway.\n"; } elsif ($state eq "Not-For-Us") { print "$name: not suitable for this architecture anyway. Skipping.\n"; return; } elsif ($state eq "Failed-Removed") { print "$name: failed previously and doesn't need building. Skipping.\n"; return; } elsif ($state eq "Installed") { print "$name: Is already installed in archive. Skipping.\n"; return; } elsif ($state eq "Uploaded") { print "$name: Is already uploaded. Skipping.\n"; return; } elsif ($pkg->{'builder'} && $user ne $pkg->{'builder'}) { print "$name: not taken by you, but by ". "$pkg->{'builder'}. Skipping.\n"; return; } elsif ( !pkg_version_eq($pkg,$version)) { print "$name: version mismatch ". "($pkg->{'version'} ". "by $pkg->{'builder'})\n"; return; } elsif ($fail_reason =~ /^\s*$/ || !parse_deplist( $fail_reason, 1 )) { print "$name: Bad dependency list\n"; return; } change_state( \$pkg, 'Dep-Wait' ); $pkg->{'builder'} = $user; if (defined $pkg->{'permbuildpri'}) { $pkg->{'buildpri'} = $pkg->{'permbuildpri'}; } else { delete $pkg->{'buildpri'}; } my $deplist = parse_deplist( $pkg->{'depends'} ); my $new_deplist = parse_deplist( $fail_reason ); # add new dependencies, maybe overwriting old entries foreach (keys %$new_deplist) { $deplist->{$_} = $new_deplist->{$_}; } $pkg->{'depends'} = build_deplist($deplist); log_ta( $pkg, "--dep-wait" ); update_source_info($pkg); print "$name: registered as waiting for dependencies\n" if $verbose; } sub set_one_update { my $name = shift; my $version = shift; my $pkg = get_source_info($name); if (!defined($pkg)) { print "$name: not registered yet.\n"; return; } $pkg->{'version'} =~ s/\+b[0-9]+$//; log_ta( $pkg, "--update" ); update_source_info($pkg); } # for sorting priorities and sections BEGIN { %prioval = ( required => -5, important => -4, standard => -3, optional => -2, extra => -1, unknown => -1 ); %sectval = ( libs => -200, 'debian-installer' => -199, base => -198, devel => -197, kernel => -196, shells => -195, perl => -194, python => -193, graphics => -192, admin => -191, utils => -190, x11 => -189, editors => -188, net => -187, httpd => -186, mail => -185, news => -184, tex => -183, text => -182, web => -181, vcs => -180, doc => -179, localizations => -178, interpreters => -177, ruby => -176, java => -175, ocaml => -174, lisp => -173, haskell => -172, 'cli-mono' => -171, gnome => -170, kde => -169, xfce => -168, gnustep => -167, database => -166, video => -165, debug => -164, games => -163, misc => -162, fonts => -161, otherosfs => -160, oldlibs => -159, libdevel => -158, sound => -157, math => -156, 'gnu-r' => -155, science => -154, comm => -153, electronics => -152, hamradio => -151, embedded => -150, php => -149, zope => -148, ); foreach my $i (keys %sectval) { $sectval{"contrib/$i"} = $sectval{$i}+40; $sectval{"non-free/$i"} = $sectval{$i}+80; } $sectval{'unknown'} = -165; } sub sort_list_func { my $map_funcs = { 'C' => ['<->', sub { return $_[0]->{'calprio'}; }], 'W' => ['<->', sub { return $_[0]->{'state_days'}; }], 'P' => ['<->', sub { return $_[0]->{'buildpri'}; }], 'p' => ['<=>', sub { return $prioval{$_[0]->{'priority'}//""}//0; }], 's' => ['<=>', sub { return $sectval{$_[0]->{'section'}//""}//0; }], 'n' => ['cmp', sub { return $_[0]->{'package'}; }], 'b' => ['cmp', sub { return $_[0]->{'builder'}; }], 'c' => ['<=>', sub { return ($_[0]->{'notes'}//"" =~ /^(out-of-date|partial)/) ? 0: ($_[0]->{'notes'}//"" =~ /^uncompiled/) ? 2 : 1; }], 'S' => ['<->', sub { return isin($_[0]->{'priority'}, qw(required important standard)); }], 'T' => ['<->', sub { return $_[0]->{'state_time'} % 86400;} ], # Fractions of a day }; foreach my $letter (split( //, $list_order )) { my $r; $r = (&{$map_funcs->{$letter}[1]}($b)//0 ) <=> (&{$map_funcs->{$letter}[1]}($a)//0 ) if $map_funcs->{$letter}[0] eq '<->'; $r = (&{$map_funcs->{$letter}[1]}($a)//0 ) <=> (&{$map_funcs->{$letter}[1]}($b)//0 ) if $map_funcs->{$letter}[0] eq '<=>'; $r = (&{$map_funcs->{$letter}[1]}($a)//"") cmp (&{$map_funcs->{$letter}[1]}($b)//"") if $map_funcs->{$letter}[0] eq 'cmp'; return $r if $r != 0; } return 0; } sub calculate_prio { my $priomap = $yamlmap->{priority}; my $pkg = shift; my @s=split("/", $pkg->{'section'}); $pkg->{'component'} = $s[0] if $s[1]; $pkg->{'component'} ||= 'main'; $pkg->{'calprio'} = 0; foreach my $k (keys %{$priomap->{keys}}) { $pkg->{'calprio'} += $priomap->{keys}->{$k}{$pkg->{$k}} if $pkg->{$k} and $priomap->{keys}->{$k}{$pkg->{$k}}; } my $days = $pkg->{'state_days'}; $days = $priomap->{'waitingdays'}->{'min'} if $priomap->{'waitingdays'}->{'min'} and $days < $priomap->{'waitingdays'}->{'min'}; $days = $priomap->{'waitingdays'}->{'max'} if $priomap->{'waitingdays'}->{'max'} and $days > $priomap->{'waitingdays'}->{'max'}; my $scale = $priomap->{'waitingdays'}->{'scale'} || 1; $pkg->{'calprio'} += $days * $scale; my $btime = max($pkg->{'anytime'}//0, $pkg->{'successtime'}//0); my $bhours = $btime ? int($btime/3600) : ($priomap->{'buildhours'}->{'default'} || 2); $bhours = $priomap->{'buildhours'}->{'min'} if $priomap->{'buildhours'}->{'min'} and $bhours < $priomap->{'buildhours'}->{'min'}; $bhours = $priomap->{'buildhours'}->{'max'} if $priomap->{'buildhours'}->{'max'} and $bhours > $priomap->{'buildhours'}->{'max'}; $scale = $priomap->{'buildhours'}->{'scale'} || 1; $pkg->{'calprio'} -= $bhours * $scale; $pkg->{'calprio'} += $pkg->{'permbuildpri'} if $pkg->{'permbuildpri'}; $pkg->{'calprio'} += $pkg->{'buildpri'} if $pkg->{'buildpri'}; return $pkg; } sub seconds2time { my $t = shift; return "" unless $t; my $sec = $t % 60; my $min = int($t/60) % 60; my $hours = int($t / 3600); return sprintf("%d:%02d:%02d", $hours, $min, $sec) if $hours; return sprintf("%d:%02d", $min, $sec); } sub use_fmt { my $r; if (ref($_[0]) eq 'CODE') { $r = &{$_[0]}; } else { $r = $_[0]; } shift; my $t = shift; $r ||= ""; return $r unless $t; my $pkg = shift; my $var = shift; if (substr($t,0,1) eq '!') { $t = substr($t,1); return "" if $r; } else { return "" unless $r; } if ($t =~ /%/) { return print_format($t, $pkg, $var); } return $t; } sub make_fmt { my $c = shift; my $pkg = shift; my $var = shift; return sub { use_fmt($c, $_[0], $pkg, $var); } }; sub print_format { my $printfmt = shift; my $pkg = shift; my $var = shift; =pod Within an format string, the following values are allowed (need to be preceded by %). This can be combined to e.g. wanna-build --format='wanna-build -A %a --give-back %p_%v' -A mipsel --list=failed a Architecture c section (e.g. libs or utils) D in case of BD-Uninstallable the reason for the uninstallability d distribution E in case of Dep-Wait the packages being waited on, in case of Needs-Build the number in the queue F in case of Failed the fail reason n newline o time of last successful build (seconds) O time of last successful build (formated) P previous state p Package name q time of last build (seconds) Q time of last build (formated) r max time of last (successful) build (seconds) R max time of last (successful) build (formated) S Package state s Time in this state in full seconds since epoch t time of state change T time since state change u Builder (e.g. buildd_mipsel-rem) v Package version V full Package version (i.e. with +b.., = %v%{+b}B%B X the string normally between [], e.g. optional:out-of-date:calprio{61}:days{25} %{Text}? print Text in case ? is not empty; ? is never printed %{!Text}? print Text in case ? is empty; ? is never printed Text could contain further %. To start with !, use %! =cut return stringf($printfmt, ( 'p' => make_fmt( $pkg->{'package'}, $pkg, $var), 'a' => make_fmt( $arch, $pkg, $var), 's' => make_fmt( sub { return floor(str2time($pkg->{'state_change'})); }, $pkg, $var), 'v' => make_fmt( $pkg->{'version'}, $pkg, $var), 'V' => make_fmt( sub { $pkg->{'binary_nmu_version'} ? $pkg->{'version'}."+b".$pkg->{'binary_nmu_version'} : $pkg->{'version'} }, $pkg, $var), 'S' => make_fmt( $pkg->{'state'}, $pkg, $var), 'u' => make_fmt( $pkg->{'builder'}, $pkg, $var), 'X' => make_fmt( sub { no warnings; my $c = "$pkg->{'priority'}:$pkg->{'notes'}"; $c .= ":PREV-FAILED" if $pkg->{'previous_state'} && $pkg->{'previous_state'} =~ /^Failed/; $c .= ":bp{" . $pkg->{'buildpri'} . "}" if defined $pkg->{'buildpri'}; $c .= ":binNMU{" . $pkg->{'binary_nmu_version'} . "}" if defined $pkg->{'binary_nmu_version'}; $c .= ":calprio{". $pkg->{'calprio'}."}"; $c .= ":days{". $pkg->{'state_days'}."}"; return $c; }, $pkg, $var), 'c' => make_fmt( $pkg->{'section'}, $pkg, $var), 'P' => make_fmt( $pkg->{'previous_state'} || "unknwon", $pkg, $var), 'E' => make_fmt( sub { return $pkg->{'depends'} if $pkg->{'state'} eq "Dep-Wait"; return $var->{scnt}{'Needs-Build'} + 1 if $pkg->{'state'} eq 'Needs-Build'; return ""; }, $pkg, $var), 'F' => make_fmt( sub { return "" unless $pkg->{'failed'}; my $failed = $pkg->{'failed'}; $failed =~ s/\\/\\\\/g; return $pkg->{'package'}."#".$arch."-failure\n ". join("\\0a",split("\n",$failed))."\\0a\n"; }, $pkg, $var), 'D' => make_fmt( sub { return "" unless $pkg->{'bd_problem'}; return $pkg->{'package'}."#".$arch."-bd-problem\n". join("\\0a",split("\n",$pkg->{'bd_problem'}))."\\0a\n"; }, $pkg, $var), 'B' => make_fmt( sub { return $pkg->{'binary_nmu_version'} if defined $pkg->{'binary_nmu_version'}; }, $pkg, $var), 'd' => make_fmt( $pkg->{'distribution'}, $pkg, $var), 't' => make_fmt( $pkg->{'state_change'}, $pkg, $var), 'T' => make_fmt( sub { return seconds2time(time() - floor(str2time($pkg->{'state_change'}))); }, $pkg, $var), 'o' => make_fmt( $pkg->{'successtime'}, $pkg, $var), 'O' => make_fmt( sub { return seconds2time ( $pkg->{'successtime'}); }, $pkg, $var), 'q' => make_fmt( $pkg->{'anytime'}, $pkg, $var), 'Q' => make_fmt( sub { return seconds2time ( $pkg->{'anytime'}); }, $pkg, $var), 'r' => make_fmt( sub { my $c = max($pkg->{'successtime'}//0, $pkg->{'anytime'}//0); return $c if $c; return; }, $pkg, $var), 'R' => make_fmt( sub { return seconds2time ( max($pkg->{'successtime'}//0, $pkg->{'anytime'}//0)); }, $pkg, $var), )); } sub list_packages { my $state = shift; my( $name, $pkg, @list ); my $cnt = 0; my %scnt; my $ctime = time; my $db = get_all_source_info(state => $state, user => $user, list_min_age => $list_min_age); foreach $name (keys %$db) { next if $name =~ /^_/; push @list, calculate_prio($db->{$name}); } # filter components @list = grep { my $i = $_->{'component'}; grep { $i eq $_ } split /[, ]+/, $yamlmap->{"restrict"}{'component'} } @list; # extra depends / conflicts only from api 1 on @list = grep { !$_->{'extra_depends'} and !$_->{'extra_conflicts'} } @list if $api < 1 ; # first adjust ownprintformat, then set printformat accordingly $printformat ||= $yamlmap->{"format"}{$ownprintformat} if $ownprintformat; $printformat ||= $yamlmap->{"format"}{"default"}{$state}; $printformat ||= $yamlmap->{"format"}{"default"}{"default"}; undef $printformat if ($ownprintformat && $ownprintformat eq 'none'); foreach $pkg (sort sort_list_func @list) { if ($printformat) { print print_format($printformat, $pkg, {'cnt' => $cnt, 'scnt' => \%scnt})."\n"; ++$cnt; $scnt{$pkg->{'state'}}++; next; } print print_format("%c/%p_%v", $pkg, {}); print print_format(": %S", $pkg, {}) if $state eq "all"; print print_format("%{ by }u%u", $pkg, {}) if $pkg->{'state'} ne "Needs-Build"; print print_format(" [%X]\n", $pkg, {}); print " Reasons for failing:\n", join("\n ",split("\n",$pkg->{'failed'})), "\n" if $pkg->{'state'} =~ /^Failed/; print " Dependencies: $pkg->{'depends'}\n" if $pkg->{'state'} eq "Dep-Wait"; print " Reasons for BD-Uninstallable:\n ", join("\n ",split("\n",$pkg->{'bd_problem'})), "\n" if $pkg->{'state'} eq "BD-Uninstallable"; print " Previous state was $pkg->{'previous_state'}\n" if $verbose && $pkg->{'previous_state'}; print " No previous state recorded\n" if $verbose && !$pkg->{'previous_state'}; print " State changed at $pkg->{'state_change'}\n" if $verbose && $pkg->{'state_change'}; print " Previous state $pkg->{'previous_state'} left $pkg->{'state_time'} ago\n" if $verbose && $pkg->{'previous_state'}; print " Previous failing reasons:\n ", join("\n ",split("\n",$pkg->{'old_failed'})), "\n" if $verbose && $pkg->{'old_failed'}; ++$cnt; $scnt{$pkg->{'state'}}++ if $state eq "all"; } if ($state eq "all" && !$printformat) { foreach (sort keys %scnt) { print "Total $scnt{$_} package(s) in state $_.\n"; } } print "Total $cnt package(s)\n" unless $printformat; } sub info_packages { my( $name, $pkg, $key, $dist ); my @firstkeys = qw(package version builder state section priority installed_version previous_state state_change); my @dists = $info_all_dists ? keys %distributions : ($distribution); my %beautykeys = ( 'package' => 'Package', 'version' => 'Version', 'builder' => 'Builder', 'state' => 'State', 'section' => 'Section', 'priority' => 'Priority', 'installed_version' => 'Installed-Version', 'previous_state' => 'Previous-State', 'state_change' => 'State-Change', 'bd_problem' => 'BD-Problem', 'binary_nmu_changelog' => 'Binary-NMU-Changelog', 'binary_nmu_version' => 'Binary-NMU-Version', 'buildpri' => 'BuildPri', 'depends' => 'Depends', 'failed' => 'Failed', 'notes' => 'Notes', 'distribution' => 'Distribution', 'old_failed' => 'Old-Failed', 'permbuildpri' => 'PermBuildPri', 'rel' => 'Rel', 'calprio' => 'CalculatedPri', 'state_days' => 'State-Days', 'state_time' => 'State-Time', 'successtime' => 'Success-build-time', 'anytime' => 'Build-time', 'extra_depends' => 'Extra-Dependencies', 'extra_conflicts' => 'Extra-Conflicts', 'build_arch_all' => 'Build-Arch-All', ); foreach $name (@_) { $name =~ s/_.*$//; # strip version foreach $dist (@dists) { my $pname = "$name" . ($info_all_dists ? "($dist)" : ""); $pkg = get_readonly_source_info($name); if (!defined( $pkg )) { print "$pname: not registered\n"; next; } $pkg = calculate_prio($pkg); print "$pname:\n"; foreach $key (@firstkeys) { next if !defined $pkg->{$key}; my $val = $pkg->{$key}; chomp( $val ); $val = "\n$val" if isin( $key, qw(Failed Old-Failed)); $val =~ s/\n/\n /g; my $print_key = $key; $print_key = $beautykeys{$print_key} if $beautykeys{$print_key}; printf " %-20s: %s\n", $print_key, $val; } foreach $key (sort keys %$pkg) { next if isin( $key, @firstkeys ); my $val = $pkg->{$key}; next if !defined($val); chomp( $val ); $val = "\n$val" if isin( $key, qw(Failed Old-Failed)); $val =~ s/\n/\n /g; my $print_key = $key; $print_key = $beautykeys{$print_key} if $beautykeys{$print_key}; printf " %-20s: %s\n", $print_key, $val; } } } } sub forget_packages { my( $name, $pkg, $key, $data ); foreach $name (@_) { $name =~ s/_.*$//; # strip version $pkg = get_source_info($name); if (!defined( $pkg )) { print "$name: not registered\n"; next; } $data = ""; foreach $key (sort keys %$pkg) { my $val = $pkg->{$key}; chomp( $val ); $val =~ s/\n/\n /g; $data .= sprintf " %-20s: %s\n", $key, $val; } send_mail( $conf::db_maint, "$name deleted from DB " . table_name() . " " . $distribution, "The package '$name' has been deleted from the database ". "by $user.\n\n". "Data registered about the deleted package:\n". "$data\n" ) if $conf::db_maint; change_state( \$pkg, 'deleted' ); log_ta( $pkg, "--forget" ); del_source_info($name); print "$name: deleted from database\n" if $verbose; } } sub forget_users { $dbh->do("DELETE from " . user_table_name() . " WHERE distribution = ?", undef, $distribution) or die $dbh->errstr; } sub read_db { my $file = shift; print "Reading ASCII database from $file..." if $verbose >= 1; open( my $fh, '<', $file ) or die "Can't open database $file: $!\n"; local($/) = ""; # read in paragraph mode while( <$fh> ) { my( %thispkg, $name ); s/[\s\n]+$//; s/\n[ \t]+/\376\377/g; # fix continuation lines s/\376\377\s*\376\377/\376\377/og; while( /^(\S+):[ \t]*(.*)[ \t]*$/mg ) { my ($key, $val) = ($1, $2); $key =~ s/-/_/g; $key =~ tr/A-Z/a-z/; $val =~ s/\376\377/\n/g; $thispkg{$key} = $val; } check_entry( \%thispkg ); # add to db if (exists($thispkg{'package'})) { update_source_info(\%thispkg); } elsif(exists($thispkg{'user'})) { # user in import, username in database. $dbh->do('INSERT INTO ' . user_table_name() . ' (username, distribution, last_seen)' . ' values (?, ?, ?)', undef, $thispkg{'user'}, $distribution, $thispkg{'last_seen'}) or die $dbh->errstr; } } close( $fh ); print "done\n" if $verbose >= 1; } sub check_entry { my $pkg = shift; my $field; return if $op_mode eq "manual-edit"; # no checks then # check for required fields if (exists $pkg->{'user'}) { return; } if (!exists $pkg->{'package'}) { print STDERR "Bad entry: ", join( "\n", map { "$_: $pkg->{$_}" } keys %$pkg ), "\n"; die "Database entry lacks package or username field\n"; } # if no State: field, generate one (for old db compat) if (!exists($pkg->{'state'})) { $pkg->{'state'} = exists $pkg->{'failed'} ? 'Failed' : 'Building'; } if (!exists $pkg->{'version'} and $pkg->{'state'} ne 'Not-For-Us') { die "Database entry for $pkg->{'package'} lacks Version: field\n"; } # check state field die "Bad state $pkg->{'state'} of package $pkg->{Package}\n" if !isin( $pkg->{'state'}, qw(Needs-Build Building Built Build-Attempted Uploaded Installed Dep-Wait Dep-Wait-Removed Failed Failed-Removed Not-For-Us BD-Uninstallable Auto-Not-For-Us ) ); } sub export_db { my $file = shift; my($name,$pkg,$key); print "Writing ASCII database to $file..." if $verbose >= 1; open( my $fh, '>', $file ) or die "Can't open export $file: $!\n"; my $db = get_all_source_info(); foreach $name (keys %$db) { next if $name =~ /^_/; my $pkg = $db->{$name}; foreach $key (keys %{$pkg}) { my $val = $pkg->{$key}; next if !defined($val); $val =~ s/\n*$//; $val =~ s/^/ /mg; $val =~ s/^ +$/ ./mg; print $fh "$key: $val\n"; } print $fh "\n"; } close( $fh ); print "done\n" if $verbose >= 1; } sub change_state { my $pkgr = shift; my $pkg = $$pkgr; my $newstate = shift; my $state = \$pkg->{'state'}; return if defined($$state) and $$state eq $newstate; $pkg->{'previous_state'} = $$state if defined($$state); $pkg->{'state_change'} = $curr_date; $pkg->{'do_state_change'} = 1; if (defined($$state) and $$state eq 'Failed') { $pkg->{'old_failed'} = "-"x20 . " $pkg->{'version'} " . "-"x20 . "\n" . ($pkg->{'failed'} // ""). "\n" . ($pkg->{'old_failed'} // ""); delete $pkg->{'failed'}; } if (defined($$state) and $$state eq 'BD-Uninstallable') { delete $pkg->{'bd_problem'}; } $$state = $newstate; } sub log_ta { my $pkg = shift; my $action = shift; my $dist = $distribution; my $str; my $prevstate; $prevstate = $pkg->{'previous_state'}; $str = "$action($dist): $pkg->{'package'}_$pkg->{'version'} ". "changed from $prevstate to $pkg->{'state'} ". "by $real_user as $user"; if ($simulate) { printf "update transactions: %s %s %s %s %s %s %s %s\n", $pkg->{'package'}, $distribution, $pkg->{'version'}, $action, $prevstate, $pkg->{'state'}, $real_user, $user; return; } $dbh->do('INSERT INTO ' . transactions_table_name() . ' (package, distribution, version, action, ' . ' prevstate, state, real_user, set_user, time) ' . ' values (?, ?, ?, ?, ?, ?, ?, ?, ?)', undef, $pkg->{'package'}, $distribution, $pkg->{'version'}, $action, $prevstate, $pkg->{'state'}, $real_user, $user, 'now()') or die $dbh->errstr; if (!($prevstate eq 'Failed' && $pkg->{'state'} eq 'Failed')) { $str .= " (with --override)" if $opt_override; $mail_logs .= "$str\n"; } } sub send_mail { my $to = shift; my $subject = shift; my $text = shift; my $from = $conf::db_maint; my $domain = $conf::buildd_domain; $from .= "\@$domain" if $from !~ /\@/; $to .= '@' . $domain if $to !~ /\@/; $text =~ s/^\.$/../mg; local $SIG{'PIPE'} = 'IGNORE'; open( my $pipe, '|-', "$conf::mailprog -oem $to" ) or die "Can't open pipe to $conf::mailprog: $!\n"; chomp $text; print $pipe "From: $from\n"; print $pipe "Subject: $subject\n\n"; print $pipe "$text\n"; close( $pipe ); } # for parsing input to dep-wait sub parse_deplist { my $deps = shift; my $verify = shift; my %result; foreach (split( /\s*,\s*/, $deps )) { if ($verify) { # verification requires > starting prompts, no | crap if (!/^(\S+)\s*(\(\s*(>(?:[>=])?)\s*(\S+)\s*\))?\s*$/) { return 0; } next; } my @alts = split( /\s*\|\s*/, $_ ); # Anything with an | is ignored, as it can be configured on a # per-buildd basis what will be installed next if $#alts != 0; $_ = shift @alts; if (!/^(\S+)\s*(\(\s*(>=|=|==|>|>>|<<|<=)\s*(\S+)\s*\))?\s*$/) { warn( "parse_deplist: bad dependency $_\n" ); next; } my($dep, $rel, $relv) = ($1, $3, $4); $rel = ">>" if defined($rel) and $rel eq ">"; $result{$dep}->{'package'} = $dep; if ($rel && $relv) { $result{$dep}->{'rel'} = $rel; $result{$dep}->{'version'} = $relv; } } return 1 if $verify; return \%result; } sub build_deplist { my $list = shift; my($key, $result); foreach $key (keys %$list) { $result .= ", " if $result; $result .= $key; $result .= " ($list->{$key}->{'rel'} $list->{$key}->{'version'})" if $list->{$key}->{'rel'} && $list->{$key}->{'version'}; } return $result; } sub filterarch { return "" unless $_[0]; return Dpkg::Deps::parse($_[0], ("reduce_arch" => 1, "host_arch" => $_[1]))->dump(); } sub wb_edos_builddebcheck { # Copyright (C) 2008 Ralf Treinen # 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, version 2 of the License. # integrated into wanna-builds code by Andreas Barth 2010 my $args = shift; my $sourceprefix="source---"; my $architecture=$args->{'arch'}; my $edosoptions = "-failures -explain -quiet"; my $packagefiles = $args->{'pkgs'}; my $sourcesfile = $args->{'src'}; my $packagearch=""; foreach my $packagefile (@$packagefiles) { open(my $fh,'<', $packagefile); while (<$fh>) { next unless /^Architecture/; next if /^Architecture:\s*all/; /Architecture:\s*([^\s]*)/; if ($packagearch eq "") { $packagearch = $1; } elsif ( $packagearch ne $1) { return "Package file contains different architectures: $packagearch, $1"; } } close $fh; } if ( $architecture eq "" ) { if ( $packagearch eq "" ) { return "No architecture option given, " . "and no non-all architecture found in the Packages file"; } else { $architecture = $packagearch; } } else { if ( $packagearch ne "" & $architecture ne $packagearch) { return "Architecture option is $architecture ". "but the package file contains architecture $packagearch"; } } print "calling: edos-debcheck $edosoptions < $sourcesfile ".join('', map {" '-base FILE' ".$_ } @$packagefiles)."\n"; open(my $result_cmd, '-|', "edos-debcheck $edosoptions < $sourcesfile ".join('', map {" '-base FILE' ".$_ } @$packagefiles)); my $explanation=""; my $result={}; my $binpkg=""; while (<$result_cmd>) { # source---pulseaudio (= 0.9.15-4.1~bpo50+1): FAILED # source---pulseaudio (= 0.9.15-4.1~bpo50+1) depends on missing: # - libltdl-dev (>= 2.2.6a-2) # source---libcanberra (= 0.22-1~bpo50+1): FAILED # source---libcanberra (= 0.22-1~bpo50+1) depends on missing: # - libltdl-dev # - libltdl7-dev (>= 2.2.6) if (/^\s+/) { s/^(\s*)$sourceprefix(.*)depends on/$1$2build-depends on/o; s/^(\s*)$sourceprefix(.*) and (.*) conflict/$1$2 build-conflicts with $3/o; $explanation .= $_; } else { if (/^$sourceprefix(.*) \(.*\): FAILED/o) { $result->{$binpkg} = $explanation if $binpkg; $explanation = ""; $binpkg = $1; } elsif (/^(depwait---.*) \(.*\): FAILED/o) { $result->{$binpkg} = $explanation if $binpkg; $explanation = ""; $binpkg = $1; } else { # else something broken is happening #print "ignoring $_\n"; 1; } } } close $result_cmd; $result->{$binpkg} = $explanation if $binpkg; return $result; } sub call_edos_depcheck { return if $simulate_edos; my $args = shift; my $srcs = $args->{'srcs'}; my $key; return if defined ($distributions{$distribution}{noadw}) && not defined $args->{'depwait'}; # We need to check all of needs-build, as any new upload could make # something in needs-build have uninstallable deps # We also check everything in bd-uninstallable, as any new upload could # make that work again my (%interesting_packages, %interesting_packages_depwait); my $db = get_all_source_info(); foreach $key (keys %$db) { my $pkg = $db->{$key}; if (defined $pkg and isin($pkg->{'state'}, qw/Needs-Build BD-Uninstallable/) and not defined ($distributions{$distribution}{noadw})) { $interesting_packages{$key} = undef; } if (defined $pkg and isin($pkg->{'state'}, qw/Dep-Wait/) and defined $args->{'depwait'}) { $interesting_packages_depwait{$key} = undef; # we always check for BD-Uninstallability in depwait - could be that depwait is satisfied but package is uninstallable $interesting_packages{$key} = undef unless defined ($distributions{$distribution}{noadw}); } } #print "I would look at these sources with edos-depcheck:\n"; #print join " ", keys %interesting_packages,"\n"; return unless %interesting_packages || %interesting_packages_depwait; my $tmpfile_pattern = "/tmp/wanna-build-interesting-sources-$distribution.$$-XXXXX"; use File::Temp qw/ tempfile /; my ($SOURCES, $tmpfile) = tempfile( $tmpfile_pattern, UNLINK => 1 ); for my $key (keys %interesting_packages) { my $pkg = $db->{$key}; # we print the source files as binary ones (with "source---"-prefixed), # so we can try if these "binary" packages are installable. # If such a "binary" package is installable, the corresponding source package is buildable. print $SOURCES "Package: source---$key\n"; print $SOURCES "Version: $pkg->{'version'}\n"; my $t = &filterarch($srcs->{$key}{'dep'} || $srcs->{$key}{'depends'}, $arch); my $tt = &filterarch($pkg->{'extra_depends'}, $arch); $t = $t ? ($tt ? "$t, $tt" : $t) : $tt; print $SOURCES "Depends: $t\n" if $t; my $u = &filterarch($srcs->{$key}{'conf'} || $srcs->{$key}{'conflicts'}, $arch); my $uu = &filterarch($pkg->{'extra_conflicts'}, $arch); $u = $u ? ($uu ? "$u, $uu" : $u) : $uu; print $SOURCES "Conflicts: $u\n" if $u; print $SOURCES "Architecture: all\n"; print $SOURCES "\n"; } for my $key (keys %interesting_packages_depwait) { my $pkg = $db->{$key}; # we print the source files as binary ones (with "depwait---"-prefixed), # so we can try if these "binary" packages are installable. # If such a "binary" package is installable, the corresponding source package goes out of depwait print $SOURCES "Package: depwait---$key\n"; print $SOURCES "Version: $pkg->{'version'}\n"; print $SOURCES "Depends: $pkg->{'depends'}\n"; print $SOURCES "Architecture: all\n"; print $SOURCES "\n"; } close $SOURCES; my $edosresults = wb_edos_builddebcheck({'arch' => $args->{'arch'}, 'pkgs' => $args->{'pkgs'}, 'src' => $tmpfile}); if (ref($edosresults) eq 'HASH') { foreach my $key (grep { $_ !~ /^depwait---/ } keys %$edosresults) { if (exists $interesting_packages{$key}) { $interesting_packages{$key} = $edosresults->{$key}; } else { #print "TODO: edos reported a package we do not care about now\n" if $verbose; } } foreach my $key (grep { $_ =~ /^depwait---/ } keys %$edosresults) { $key =~ /^depwait---(.*)/ and $key = $1; if (exists $interesting_packages_depwait{$key}) { $interesting_packages_depwait{$key} = $edosresults->{"depwait---".$key}; } else { #print "TODO: edos reported a package we do not care about now\n" if $verbose; } } } else { # if $edosresults isn't an hash, then something went wrong and the string is the error message print "ERROR: Could not run wb-edos-builddebcheck. I am continuing, assuming\n" . "all packages have installable build-dependencies." } unlink( $tmpfile ); for my $key (keys %interesting_packages) { next if defined $interesting_packages_depwait{$key}; my $pkg = $db->{$key}; my $change = (defined $interesting_packages{$key} and $pkg->{'state'} eq 'Needs-Build') || (not defined $interesting_packages{$key} and $pkg->{'state'} eq 'BD-Uninstallable'); my $problemchange = ($interesting_packages{$key}//"") ne ($pkg->{'bd_problem'}//""); if ($change) { if (defined $interesting_packages{$key}) { change_state( \$pkg, 'BD-Uninstallable' ); $pkg->{'bd_problem'} = $interesting_packages{$key}; } else { change_state( \$pkg, 'Needs-Build' ); } } if ($problemchange) { if (defined $interesting_packages{$key}) { $pkg->{'bd_problem'} = $interesting_packages{$key}; } } if ($change) { log_ta( $pkg, "--merge-all (edos)" ) unless $simulate; print "edos-builddebchange changed state of ${key}_$pkg->{'version'} ($args->{'arch'}) to $pkg->{'state'}\n" if $verbose || $simulate; } if ($change || $problemchange) { update_source_info($pkg) unless $simulate; } } for my $key (keys %interesting_packages_depwait) { if ($interesting_packages_depwait{$key}) { print "dep-wait for $key ($args->{'arch'}) not fullfiled yet\n" if $verbose || $simulate; next; } my $pkg = $db->{$key}; if (defined $interesting_packages{$key}) { change_state( \$pkg, 'BD-Uninstallable' ); $pkg->{'bd_problem'} = $interesting_packages{$key}; } else { change_state( \$pkg, 'Needs-Build' ); } log_ta( $pkg, "edos_depcheck: depwait" ) unless $simulate; update_source_info($pkg) unless $simulate; print "edos-builddebchange changed state of ${key}_$pkg->{'version'} ($args->{'arch'}) from dep-wait to $pkg->{'state'}\n" if $verbose || $simulate; } } sub usage { my $prgname; ($prgname = $0) =~ s,^.*/,,; print <<"EOF"; Usage: $prgname Options: -v, --verbose: Verbose execution. -A arch: Architecture this operation is for. --take: Take package for building [default operation] -f, --failed: Record in database that a build failed due to deficiencies in the package (that aren't fixable without a new source version). -u, --uploaded: Record in the database that the packages build correctly and were uploaded. -n, --no-build: Record in the database that the packages aren't desired for this architecture and shouldn't appear in listings even if they're out of date. --dep-wait: Record in the database that the packages are waiting for some source dependencies to become available --binNMU num: Schedule a re-build of the package with unchanged source, but a new version number (source-version + "+b") --give-back: Mark a package as ready to build that is in state Building, Built or Build-Attempted. To give back a package in state Failed, use --override. This command will actually put the package in state BD-Uninstallable, until the installability of its Build-Dependencies were verified. This happens at each call of --merge-all, usually every 15 minutes. -i SRC_PKG, --info SRC_PKG: Show information for source package -l STATE, --list=STATE: List all packages in state STATE; can be combined with -U to restrict to a specific user; STATE can also be 'all' -m MESSAGE, --message=MESSAGE: Give reason why package failed or source dependency list (used with -f, --dep-wait, and --binNMU) -o, --override: Override another user's lock on a package, i.e. take it over; a notice mail will be sent to the other user -U USER, --user=USER: select user name for which listings should apply, if not given all users are listed. if -l is missing, set user name to be entered in db; usually automatically choosen --import FILE: Import database from a ASCII file FILE --export FILE: Export database to a ASCII file FILE The remaining arguments (depending on operation) usually start with "name_version", the trailer is ignored. This allows to pass the names of .dsc files, for which file name completion can be used. --merge-packages and --merge-quinn take Package/quin--diff file names on the command line or read stdin. --list needs nothing more on the command line. --info takes source package names (without version). EOF exit 1; } sub pkg_version_eq { my $pkg = shift; my $version = shift; return 1 if (defined $pkg->{'binary_nmu_version'}) and version_compare(binNMU_version($pkg->{'version'}, $pkg->{'binary_nmu_version'}),'=', $version); return version_compare( $pkg->{'version'}, "=", $version ); } sub table_name { return '"' . $arch . $schema_suffix . '".packages'; } sub user_table_name { return '"' . $arch . $schema_suffix . '".users'; } sub transactions_table_name { return '"' . $arch . $schema_suffix . '".transactions'; } sub pkg_history_table_name { return '"' . $arch . $schema_suffix . '".pkg_history'; } sub get_readonly_source_info { my $name = shift; # SELECT FLOOR(EXTRACT('epoch' FROM age(localtimestamp, '2010-01-22 23:45')) / 86400) -- change to that? my $q = "SELECT rel, priority, state_change, permbuildpri, section, buildpri, failed, state, binary_nmu_changelog, bd_problem, version, package, distribution, installed_version, notes, builder, old_failed, previous_state, binary_nmu_version, depends, extract(days from date_trunc('days', now() - state_change)) as state_days, floor(extract(epoch from now()) - extract(epoch from state_change)) as state_time" . ", (SELECT max(build_time) FROM ".pkg_history_table_name()." WHERE pkg_history.package = packages.package AND pkg_history.distribution = packages.distribution AND result = 'successful') AS successtime" . ", (SELECT max(build_time) FROM ".pkg_history_table_name()." WHERE pkg_history.package = packages.package AND pkg_history.distribution = packages.distribution ) AS anytime" . ", extra_depends, extra_conflicts, build_arch_all" . " FROM " . table_name() . ' WHERE package = ? AND distribution = ?'; my $pkg = $dbh->selectrow_hashref( $q, undef, $name, $distribution); return $pkg; } sub get_source_info { my $name = shift; return get_readonly_source_info($name) if $simulate; my $pkg = $dbh->selectrow_hashref('SELECT *, extract(days from date_trunc(\'days\', now() - state_change)) as state_days, floor(extract(epoch from now()) - extract(epoch from state_change)) as state_time FROM ' . table_name() . ' WHERE package = ? AND distribution = ?' . ' FOR UPDATE', undef, $name, $distribution); return $pkg; } sub get_all_source_info { my %options = @_; my $q = "SELECT rel, priority, state_change, permbuildpri, section, buildpri, failed, state, binary_nmu_changelog, bd_problem, version, package, distribution, installed_version, notes, builder, old_failed, previous_state, binary_nmu_version, depends, extract(days from date_trunc('days', now() - state_change)) as state_days, floor(extract(epoch from now()) - extract(epoch from state_change)) as state_time" # . ", (SELECT max(build_time) FROM ".pkg_history_table_name()." WHERE pkg_history.package = packages.package AND pkg_history.distribution = packages.distribution AND result = 'successful') AS successtime" # . ", (SELECT max(build_time) FROM ".pkg_history_table_name()." WHERE pkg_history.package = packages.package AND pkg_history.distribution = packages.distribution ) AS anytime" . ", successtime.build_time as successtime, anytime.build_time as anytime, extra_depends, extra_conflicts" . " FROM " . table_name() . " left join ( " . "select distinct on (package, distribution) build_time, package, distribution from ".pkg_history_table_name()." where result = 'successful' order by package, distribution, timestamp " . " ) as successtime using (package, distribution) " . " left join ( " . "select distinct on (package, distribution) build_time, package, distribution from ".pkg_history_table_name()." order by package, distribution, timestamp desc" . " ) as anytime using (package, distribution) " . " WHERE TRUE "; my @args = (); if ($distribution) { my @dists = split(/[, ]+/, $distribution); $q .= ' AND ( distribution = ? '.(' OR distribution = ? ' x $#dists).' )'; foreach my $d ( @dists ) { push @args, ($d); } } if ($options{state} && uc($options{state}) ne "ALL") { $q .= ' AND upper(state) = ? '; push @args, uc($options{state}); } if ($options{user} && uc($options{state}) ne "NEEDS-BUILD") { # if it's NEEDS-BUILD, we don't look at users #this basically means "this user, or no user at all": $q .= " AND (builder = ? OR upper(state) = 'NEEDS-BUILD')"; push @args, $options{user}; } if ($options{list_min_age} && $options{list_min_age} > 0) { $q .= ' AND age(state_change) > ? '; push @args, $options{list_min_age} . " days"; } if ($options{list_min_age} && $options{list_min_age} < 0) { $q .= ' AND age(state_change) < ? '; push @args, -$options{list_min_age} . " days"; } my $db = $dbh->selectall_hashref($q, 'package', undef, @args); return $db; } sub show_distribution_architectures { my $q = 'SELECT distribution, spacecat_all(architecture) AS architectures '. 'FROM distribution_architectures '. 'GROUP BY distribution'; my $rows = $dbh->selectall_hashref($q, 'distribution'); foreach my $name (keys %$rows) { print $name.': '.$rows->{$name}->{'architectures'}."\n"; } } sub show_distribution_aliases { foreach my $alias (keys %distribution_aliases) { print $alias.': '.$distribution_aliases{$alias}."\n"; } } sub update_source_info { my $pkg = shift; $pkg->{'extra_depends'} = $extra_depends if defined $extra_depends; undef $pkg->{'extra_depends'} unless $pkg->{'extra_depends'}; $pkg->{'extra_conflicts'} = $extra_conflicts if defined $extra_conflicts; undef $pkg->{'extra_conflicts'} unless $pkg->{'extra_conflicts'}; print Dumper $pkg if $verbose and $simulate; return if $simulate; my $pkg2 = get_source_info($pkg->{'package'}); if (! defined $pkg2) { add_source_info($pkg); } $dbh->do('UPDATE ' . table_name() . ' SET ' . 'version = ?, ' . 'state = ?, ' . 'section = ?, ' . 'priority = ?, ' . 'installed_version = ?, ' . 'previous_state = ?, ' . (($pkg->{'do_state_change'}) ? "state_change = now()," : ""). 'notes = ?, ' . 'builder = ?, ' . 'failed = ?, ' . 'old_failed = ?, ' . 'binary_nmu_version = ?, ' . 'binary_nmu_changelog = ?, ' . 'permbuildpri = ?, ' . 'buildpri = ?, ' . 'depends = ?, ' . 'rel = ?, ' . 'extra_depends = ?, ' . 'extra_conflicts = ?, ' . 'bd_problem = ? ' . 'WHERE package = ? AND distribution = ?', undef, $pkg->{'version'}, $pkg->{'state'}, $pkg->{'section'}, $pkg->{'priority'}, $pkg->{'installed_version'}, $pkg->{'previous_state'}, $pkg->{'notes'}, $pkg->{'builder'}, $pkg->{'failed'}, $pkg->{'old_failed'}, $pkg->{'binary_nmu_version'}, $pkg->{'binary_nmu_changelog'}, $pkg->{'permbuildpri'}, $pkg->{'buildpri'}, $pkg->{'depends'}, $pkg->{'rel'}, $pkg->{'extra_depends'}, $pkg->{'extra_conflicts'}, $pkg->{'bd_problem'}, $pkg->{'package'}, $distribution) or die $dbh->errstr; } sub add_source_info { return if $simulate; my $pkg = shift; $dbh->do('INSERT INTO ' . table_name() . ' (package, distribution) values (?, ?)', undef, $pkg->{'package'}, $distribution) or die $dbh->errstr; } sub del_source_info { return if $simulate; my $name = shift; $dbh->do('DELETE FROM ' . table_name() . ' WHERE package = ? AND distribution = ?', undef, $name, $distribution) or die $dbh->errstr; } sub get_user_info { my $name = shift; my $user = $dbh->selectrow_hashref('SELECT * FROM ' . user_table_name() . ' WHERE username = ? AND distribution = ?', undef, $name, $distribution); return $user; } sub update_user_info { return if $simulate; my $user = shift; $dbh->do('UPDATE ' . user_table_name() . ' SET last_seen = now() WHERE username = ?' . ' AND distribution = ?', undef, $user, $distribution) or die $dbh->errstr; } sub add_user_info { return if $simulate; my $user = shift; $dbh->do('INSERT INTO ' . user_table_name() . ' (username, distribution, last_seen)' . ' values (?, ?, now())', undef, $user, $distribution) or die $dbh->errstr; } sub lock_table { return if $simulate; $dbh->do('LOCK TABLE ' . table_name() . ' IN EXCLUSIVE MODE', undef) or die $dbh->errstr; } sub parse_argv { # parts the array $_[0] and $_[1] and returns the sub-array (modifies the original one) my @ret = (); my $args = shift; my $separator = shift; while($args->[0] && $args->[0] ne $separator) { push @ret, shift @$args; } shift @$args if @$args; return @ret; } sub parse_all_v3 { my $srcs = shift; my $vars = shift; my $db = get_all_source_info(); my $binary = $srcs->{'_binary'}; SRCS: foreach my $name (keys %$srcs) { next if $name eq '_binary'; # state = installed, out-of-date, uncompiled, not-for-us, auto-not-for-us my $pkgs = $srcs->{$name}; my $pkg = $db->{$name}; unless ($pkg) { next SRCS if $pkgs->{'status'} eq 'not-for-us'; my $logstr = sprintf("merge-v3 %s %s_%s (%s, %s):", $vars->{'time'}, $name, $pkgs->{'version'}, $vars->{'arch'}, $vars->{'suite'}); # does at least one binary exist in the database and is more recent - if so, we're probably just outdated, ignore the source package for my $bin (@{$pkgs->{'binary'}}) { if ($binary->{$bin} and vercmp($pkgs->{'version'}, $binary->{$bin}->{'version'}) < 0) { print "$logstr skipped because binaries (assumed to be) overwritten\n" if $verbose || $simulate; next SRCS; } } $pkg->{'package'} = $name; } my $logstr = sprintf("merge-v3 %s %s_%s", $vars->{'time'}, $name, $pkgs->{'version'}). ($pkgs->{'binnmu'} ? ";b".$pkgs->{'binnmu'} : ""). sprintf(" (%s, %s, previous: %s", $vars->{'arch'}, $vars->{'suite'}, $pkg->{'version'}//""). ($pkg->{'binary_nmu_version'} ? ";b".$pkg->{'binary_nmu_version'} : ""). ", $pkg->{'state'}):"; if (isin($pkgs->{'status'}, qw (installed related)) && $pkgs->{'version'} eq $pkg->{'version'} && ($pkgs->{'binnmu'}//0) < int($pkg->{'binary_nmu_version'}//0)) { $pkgs->{'status'} = 'out-of-date'; } if (isin($pkgs->{'status'}, qw (installed related auto-not-for-us))) { my $change = 0; my $tstate = {'installed' => 'Installed', 'related' => 'Installed', 'auto-not-for-us' => 'Auto-Not-For-Us'}->{$pkgs->{'status'}}; next if isin( $pkg->{'state'}, qw) && isin( $tstate, qw); # if the package is currently current, the status is Installed, not not-for-us if ($pkg->{'state'} ne $tstate) { change_state( \$pkg, $tstate); if (isin( $tstate, qw)) { delete $pkg->{'depends'}; delete $pkg->{'extra_depends'}; delete $pkg->{'extra_conflicts'}; } $change++; } my $attrs = { 'version' => 'version', 'installed_version' => 'version', 'binary_nmu_version' => 'binnmu', 'section' => 'section', 'priority' => 'priority' }; foreach my $k (keys %$attrs) { next if isin( $tstate, qw) && isin( $k, qw); if (($pkg->{$k}//"") ne ($pkgs->{$attrs->{$k}}//"")) { $pkg->{$k} = $pkgs->{$attrs->{$k}}; $change++; } } if (isin($pkgs->{'status'}, qw (related)) and $pkg->{'notes'} ne "related") { $pkg->{'notes'} = "related"; $change++; } if ($change) { print "$logstr set to $tstate/".($pkg->{'notes'}//"")."\n" if $verbose || $simulate; log_ta( $pkg, "--merge-v3: $tstate" ) unless $simulate; update_source_info($pkg) unless $simulate; } next; } if ($pkgs->{'status'} eq 'not-for-us') { next if isin( $pkg->{'state'}, qw(Not-For-Us Installed Failed-Removed)); if (isin( $pkg->{'state'}, qw(Failed Build-Attempted Built))) { change_state( \$pkg, "Failed-Removed" ); log_ta( $pkg, "--merge-v3: Failed-Removed" ) unless $simulate; update_source_info($pkg) unless $simulate; print "$logstr (virtually) deleted from database\n" if $verbose || $simulate; next; } print "$logstr should delete (not-for-us according to P-a-s)\n" if $verbose || $simulate || 1; # not implemented yet on purpose next; } # only uncompiled / out-of-date are left, so check if anything new if (!(isin($pkgs->{'status'}, qw (uncompiled out-of-date)))) { print "$logstr package in unknown state: $pkgs->{'status'}\n"; next SRCS; } next if $pkgs->{'version'} eq $pkg->{'version'} and $pkgs->{'binnmu'}//0 >= int($pkg->{'binary_nmu_version'}//0); next if $pkgs->{'version'} eq $pkg->{'version'} and !isin( $pkg->{'state'}, qw(Installed)); next if isin( $pkg->{'state'}, qw(Not-For-Us Failed-Removed)); if (defined( $pkg->{'state'} ) && isin( $pkg->{'state'}, qw(Building Built Build-Attempted))) { send_mail( $pkg->{'builder'}, "new version of $name (dist=$distribution)", "As far as I'm informed, you're currently building the package $name\n". "in version $pkg->{'version'}.\n\n". "Now there's a new source version $pkgs->{'version'}. If you haven't finished\n". "compiling $name yet, you can stop it to save some work.\n". "Just to inform you...\n". "(This is an automated message)\n" ) unless $simulate; print "$logstr new version while building $pkg->{'version'} -- sending mail to builder ($pkg->{'builder'})\n" if $verbose || $simulate; } change_state( \$pkg, 'Needs-Build'); $pkg->{'notes'} = $pkgs->{'status'}; $pkg->{'version'} = $pkgs->{'version'}; $pkg->{'section'} = $pkgs->{'section'}; $pkg->{'priority'} = $pkgs->{'priority'}; $pkg->{'dep'} = $pkgs->{'depends'}; $pkg->{'conf'} = $pkgs->{'conflicts'}; delete $pkg->{'builder'}; delete $pkg->{'binary_nmu_version'} unless $pkgs->{'binnmu'}; delete $pkg->{'binary_nmu_changelog'} unless $pkgs->{'binnmu'}; log_ta( $pkg, "--merge-v3: needs-build" ) unless $simulate; update_source_info($pkg) unless $simulate; print "$logstr set to needs-builds\n" if $simulate || $verbose; } foreach my $name (keys %$db) { next if $srcs->{$name}; my $pkg = $db->{$name}; my $logstr = "merge-v3 $vars->{'time'} ".$name."_$pkg->{'version'} ($vars->{'arch'}, $vars->{'suite'}, previous: $pkg->{'state'}):"; # package disappeared - delete change_state( \$pkg, 'deleted' ); log_ta( $pkg, "--merge-v3: deleted" ) unless $simulate; print "$logstr deleted from database\n" if $verbose || $simulate; del_source_info($name) unless $simulate; delete $db->{$name}; } }