123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- #! /usr/bin/perl -w
- # $Id: autoupdate.pl,v 1.36 2014/09/07 07:35:06 markus Exp $
- # Copyright (c) 2007,2008,2009
- # Markus Hennecke <markus-hennecke@markus-hennecke.de>
- #
- # Permission to use, copy, modify, and/or distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- ## no critic (RequireBriefOpen)
- use strict;
- use warnings;
- use Cwd;
- use File::Spec;
- use File::Temp qw/tempfile tempdir/;
- use FindBin;
- use Getopt::Long;
- use IO::Handle;
- use List::Util qw/reduce/;
- use OpenBSD::PackageName;
- use POSIX qw/uname :sys_wait_h :signal_h/;
- # Tries to get a variable either from the environment or /etc/mk.conf
- # falling back to the passed default value if the variable was not
- # found in either location
- sub get_mkconf_variable {
- my $var = shift;
- my $default = shift || '';
- if (defined $ENV{$var}) {
- print STDERR "$var = $ENV{$var}\n";
- return $ENV{$var};
- }
- my $mkconf = '/etc/mk.conf';
- return $default if (! -f $mkconf);
- open(my $fh, '-|', 'make', '-f', '/etc/mk.conf', '-V', $var)
- or die "Unable to query $mkconf for '$var': $!\n";
- my $result = do { local $/ = undef; <$fh> };
- close($fh);
- chomp $result;
- return $result || $default;
- }
- # Defaults to 1, can be set via the --verbose switch
- my $verbose = 1;
- # If set logs of the build are written under this directory
- my $logdir = undef;
- # If set it will show the usage information and exit afterwards
- my $show_help = 0;
- # If set to more than one it will try to build ports in parallel
- my $num_jobs = 1;
- # If set to 1 update will take those ports into account flagged for always
- # update
- my $always_update = 0;
- # If set to 1 the make clean part of the ports build will use sudo
- my $sudo_make_clean = 0;
- my $sudo = get_mkconf_variable('SUDO', '');
- # If defined via command line the script will use that file as input for
- # out of date ports. If the filename is '-' it will use stdin.
- my $out_of_date = undef;
- # The default location of the ports tree and the path where we should look
- my $portsdir = get_mkconf_variable('PORTSDIR', '/usr/ports');
- my $portsdir_path = get_mkconf_variable('PORTSDIR_PATH', "$portsdir:$portsdir/mystuff");
- # Number of jobs currently active
- my $jobs = 0;
- # Number of concurrent jobs in a build
- my $make_jobs = 1;
- # This hash will hold the PIDs of the forked build processes
- my %forked_builds = ();
- # Regex used to match a flavor
- my $regex_flavor = '((,[a-z][a-z0-9_]*)*)';
- # Regex used to match a subpackage
- my $regex_subpkg = '(,-[a-z][a-z0-9_+-]*)?';
- # List of pseudo flavors we apply if the port supports them
- my %pseudo_flavors = ();
- # Global abort flag, only set from the reaper func
- my $abort = 0;
- # Ports that failed to build
- my @aborted = ();
- # Hash of reaped child processes
- my %reaped_pids = ();
- # Default signal handler for SIGCHLD
- my $old_child_sigaction = POSIX::SigAction->new();
- # Status variables for the logging / redirect of STDOUT & STDERR
- my ($logpipe, $tee_pid, $stdout_orig, $stderr_orig);
- # This function will remove the finished forked build from the list of
- # currently build ports.
- sub REAPER {
- while ((my $wpid = waitpid(-1, &WNOHANG)) > 0) {
- my $status = $?;
- $abort = 1 if ($status != 0);
- if (!defined($tee_pid) || $wpid != $tee_pid) {
- $reaped_pids{$wpid} = $status;
- }
- else {
- $tee_pid = undef;
- }
- }
- return;
- }
- # Cleanup the log redirect to the tee child if
- sub END {
- if ($logpipe) {
- close($logpipe);
- if ($stderr_orig) {
- close(STDERR);
- open(STDERR, '>&', $stderr_orig);
- }
- if ($stdout_orig) {
- close(STDOUT);
- open(STDOUT, '>&', $stdout_orig);
- }
- }
- if ($tee_pid) {
- waitpid($tee_pid, 0);
- }
- return;
- }
- # Remove the status codes/PIDs from the reaped_pids hash periodically
- sub reap {
- my @pids = keys %reaped_pids;
- while (scalar @pids) {
- my $wpid = shift @pids;
- if (exists $forked_builds{$wpid}) {
- my $portname = $forked_builds{$wpid};
- $jobs--;
- my $status = $reaped_pids{$wpid};
- if ($status == 0) {
- print STDOUT 'Finished build of ';
- }
- else {
- print STDOUT 'Build aborted of ';
- push @aborted, ($portname);
- }
- delete $forked_builds{$wpid};
- print STDOUT $portname . "\n";
- }
- else {
- print STDOUT 'No forked process recorded for '
- . $wpid . "\n";
- }
- delete $reaped_pids{$wpid};
- }
- return;
- }
- # Reads a config file and parses the options set in the config file.
- # A list of valid options must be passed to the function.
- sub read_rc_file {
- my $valid_vars = shift;
- my ($fh, @lines, %config);
- open($fh, '<', "$ENV{HOME}/.autoupdaterc")
- or return \%config;
- while (<$fh>) {
- chomp;
- s/\t/ /g;
- push @lines, ($_);
- }
- close($fh);
- my $temp_line = '';
- my $append = 0;
- my $real_lineno = 0;
- my $lineno = 0;
- foreach (@lines) {
- $lineno++;
- # Check if we have a multiline statement
- if (m/\\$/) {
- $append++;
- s/\\$//g;
- $temp_line .= $_;
- next;
- }
- elsif ($append != 0) {
- $_ = $temp_line . $_;
- $real_lineno = $lineno - $append;
- $append = 0;
- $temp_line = '';
- }
- # Remove comments and unnecessary whitespace
- s/\#.*$//g;
- s/^[[:space:]]//g;
- s/ *= */=/g;
- s/ +/ /g;
- next if (m/^$/);
- # Check if the line has a = character, if not issue an error
- if (! m/=/) {
- print STDERR 'Not a valid config in line '
- . $real_lineno . "\n";
- return;
- }
- # Split the line at the first '='
- my $split_index = index($_, '=');
- my $var = substr($_, 0, $split_index);
- my $val = substr($_, $split_index + 1);
- # Check if the varible is valid
- if (defined $valid_vars && defined $valid_vars->{$var}) {
- $config{$var} = $val;
- }
- else {
- print STDERR 'Warning: Unknown variable "' . $var
- . '" in config file' . "\n";
- }
- }
- return \%config;
- }
- # Fill a lookup hash from config
- sub setup_lookup_hash {
- my $config_line = shift;
- my $hash = shift;
- my $info = shift;
- if (defined $config_line) {
- $config_line =~ tr/,/ /;
- foreach (split / /, $config_line) {
- print STDOUT "Adding $_ to $info\n"
- unless ($verbose < 2);
- $hash->{$_} = 1;
- }
- }
- return;
- }
- # Sets up the log directory
- sub setup_logging {
- my $config = shift;
- if (defined $config->{logging} && $config->{logging} == 1) {
- my $tmpdir = $ENV{TMPDIR} ? $ENV{TMPDIR} : '/tmp';
- $logdir = tempdir("$tmpdir/autoupdate.XXXXXXXXXX");
- my $logfile = $logdir . '/autoupdate.log';
- pipe my $pipe_out, $logpipe
- or die "Unable to create pipe: $!\n";
- my $pid = fork();
- if (! defined($pid)) {
- die "Unable to fork: $!\n";
- }
- if ($pid == 0) {
- # child
- POSIX::sigaction(&POSIX::SIGCHLD, $old_child_sigaction)
- or die "Unable to set SIGCHLD handler: $!\n";
- close($logpipe)
- or warn "Unable to close pipe endpoint in child: $!\n";
- open(STDIN, '<&=', $pipe_out)
- or die "Unable to reopen STDIN: $!\n";
- exec 'tee', $logfile;
- exit(1);
- }
- # Parent
- close($pipe_out)
- or warn "Unable to close pipe endpoint in parent: $!\n";
- open($stdout_orig, '>&', \*STDOUT)
- or die "Unable to save STDOUT: $!\n";
- open($stderr_orig, '>&', \*STDERR)
- or die "Unable to save STDERR: $!\n";
- close(STDERR);
- close(STDOUT);
- $logpipe->autoflush(1);
- open(STDERR, '>&', $logpipe)
- or die "Unable to reopen STDERR: $!\n";
- open(STDOUT, '>&', $logpipe)
- or die "Unable to reopen STDOUT: $!\n";
- STDOUT->autoflush(1);
- STDERR->autoflush(1);
- print STDOUT qq{Logging builds in "$logdir"\n};
- print STDOUT qq{Using "$logfile" as mainlog\n};
- }
- return;
- }
- # Returns the ports version or undef if no such version could be deduced
- sub get_ports_version {
- my $port = shift;
- chdir "$port" || return;
- my $show = 'show';
- my @uname = POSIX::uname();
- if ($uname[2] > 6.4) {
- $show .= '-indexed';
- }
- my $cmd = "make $show=FULLPKGNAME";
- my $pkgname = '';
- open(my $in, '-|', "$cmd 2>&1")
- or die "Unable to get version for \"$port\"\n";
- while (<$in>) {
- chomp;
- $pkgname .= $_;
- }
- close($in);
- my ($stem, $version, @flavors)
- = OpenBSD::PackageName::splitname($pkgname);
- return $version;
- }
- # Returns an integer according to the version numbers supplied.
- # -1 if the first argument is the higher version number
- # 0 if both version numbers are equal
- # +1 if the second argument is the higher version number
- sub get_higher_version {
- my ($ver1, $ver2) = @_;
- if (! defined($ver1)) {
- return 0 if (! defined($ver2));
- return 1;
- }
- elsif (! defined($ver2)) {
- return -1;
- }
- my @vers1 = split /[\.pv]/, $ver1;
- my @vers2 = split /[\.pv]/, $ver2;
- my $max_len = scalar @vers1;
- $max_len = scalar @vers2 if (scalar @vers2 > scalar @vers1);
- my $index;
- for ($index = 0; $index < $max_len; $index++) {
- return -1 if ($index >= scalar @vers2);
- return 1 if ($index >= scalar @vers1);
- my $vers1 = $vers1[$index];
- my $vers2 = $vers2[$index];
- my $cmp;
- if (($vers1 =~ /[a-z]/) || ($vers2 =~ /[a-z]/)) {
- $cmp = ($vers2 cmp $vers1);
- }
- else {
- $cmp = ($vers2 <=> $vers1);
- }
- return $cmp if ($cmp);
- }
- return 0;
- }
- # Creates an array with the name of all packages that need updates
- sub read_update_package_list {
- my $input = shift;
- my ($in, @package_list, $out);
- # Save the output from pkg_outdated in a log file
- if (defined $logdir && !defined $input) {
- open($out, '>', "$logdir/pkg_outdated");
- print STDERR "Warning: Unable to open log for pkg_outdated\n"
- if (not defined $out);
- }
- if (not defined $input) {
- my $ood_path = 'infrastructure/bin';
- my $cmd = "env PORTSDIR=\"$portsdir\" "
- . "\"$portsdir/$ood_path/pkg_outdated\" ";
- unless (open($in, '-|', "$cmd 2>/dev/null")) {
- print STDERR "Unable to execute $cmd\n";
- exit 1;
- }
- }
- elsif ($input eq '-') {
- unless (open($in, '<&=', \*STDIN)) {
- print STDERR "Unable to open stdin\n";
- exit 1;
- }
- }
- else {
- unless (open($in, '<', $input)) {
- print STDERR "Unable to open file: " . $input
- . " for reading\n";
- exit 1;
- }
- }
- while (<$in>) {
- print $out $_ if (defined $out);
- chomp;
- my $pkg = $_;
- $pkg =~ s/ +#.*$//g;
- if ($_ =~ m/\#\s+->/) {
- # Ignore ports that show up every time in pkg_outdated
- print STDERR 'Ignoring ' . $pkg . "\n";
- next;
- }
- if ($_ =~ m/\#\s+always-update\s+->\s+[0-9],/x &&
- !$always_update) {
- print STDERR "Ignoring $pkg (always update)\n";
- next;
- }
- push @package_list, ($pkg);
- }
- close($in);
- close($out) if (defined $out);
- return \@package_list;
- }
- # Creates an array with a hash for each port that needs an update
- sub create_package_information {
- my $pkg_list = shift;
- # Create an array of hashes that has the following data:
- # category => name of the ports category
- # port => name of the port (the directory in the ports tree)
- # subdir => version of the port (for ports with more than one
- # version, e.q. python)
- # flavor => list of flavors as an array reference
- # subpackage => name of the subpackage
- my @pkg_information = ();
- foreach my $pkg (@$pkg_list) {
- # Get all the variables from the port identification string
- my ($category, $port, $subdir, $flavor, $subpackage);
- # Each group in the regex has the following meaning:
- # $1 : category main which may consist of the alphabet
- # $2 : port name, which must start with at least one alpha
- # char and may have a number appended.
- # Some examples may be gtk+2, libIDL etc.
- # $3 : optional subdirs for the port, $4 will hold the last
- # $5 : an optional subpackage name, this will start with a
- # comma followed by an hyphen and have an alphanumeric
- # identifier
- # $6 : a list with optional flavors. Each flavor starts with
- # a comma and may be an alphanumeric identifier
- if ($pkg =~ m/^
- ([a-z][a-z0-9]*)\/
- ([A-Za-z][\.A-Za-z0-9-+_]*)
- ((\/[^,]*)*)?
- $regex_subpkg
- $regex_flavor/x) {
- $category = $1;
- $port = $2;
- $subdir = $3;
- $subpackage = $5;
- $flavor = $6;
- $subdir = '' if (not defined $subdir);
- $subdir =~ s/(^\/|\/$)//g;
- $subpackage = '' if (not defined $subpackage);
- $subpackage =~ s/^,//g;
- $flavor = '' if (not defined $flavor);
- $flavor =~ s/,/ /g;
- $flavor =~ s/^ //g;
- my %p = (
- category => $category,
- port => $port,
- subdir => $subdir,
- subpkg => $subpackage,
- flavor => $flavor,
- pkg => $pkg,
- );
- $p{jobs} = set_parallel_build(\%p);
- $p{dependencies} = create_dependencies_list(\%p);
- $p{deppkgs}
- = create_dependencies_hash($p{dependencies});
- $p{pseudo_flavors} = create_pseudo_flavors_list(\%p);
- add_pseudo_flavors(\%p);
- push @pkg_information, (\%p);
- }
- else {
- next if ((not defined $pkg) || ($pkg eq ''));
- print STDERR "Unknown port name: $pkg\n";
- exit 2;
- }
- print STDOUT "Category:\t$category\n"
- . "Port:\t\t$port\n"
- . "Subdir:\t\t$subdir\n"
- . "Subpackage:\t$subpackage\n"
- . "Flavor:\t\t$flavor\n\n" unless ($verbose < 5);
- }
- return \@pkg_information;
- }
- # Adds pseudo flavors to the flavors list if they appear both in the
- # pseudo_flavors array from the ports info hash and the %pseudo_flavors
- # global hash set via command line.
- sub add_pseudo_flavors {
- my $info = shift;
- my $flavors = $info->{flavor};
- foreach my $pseudo_flavor (@{$info->{pseudo_flavors}}) {
- if (exists $pseudo_flavors{$pseudo_flavor}) {
- $flavors .= ' ';
- $flavors .= $pseudo_flavor;
- }
- }
- $info->{flavor} = $flavors;
- return;
- }
- # Creates an array with pseudo flavors. We can check this array against the
- # %pseudo_flavors hash to add flavors on demand.
- sub create_pseudo_flavors_list {
- my $info = shift;
- my $cur_dir = getcwd;
- my $cmd = 'make show=PSEUDO_FLAVORS';
- # chdir into the ports directory
- my ($port, $port_dir) = find_newer_ports_dir($info);
- chdir $port_dir or die qq{Unable to change to "$port_dir": $!\n};
- open (my $in, '-|', "$cmd")
- or die qq{Unable to get pseudo flavors for "$port"\n};
- my $output;
- while (<$in>) {
- chomp;
- $output .= $_ . ' ';
- }
- chop $output;
- close($in);
- if ($? != 0) {
- die qq{Unable to get pseudo flavors for "$port\n"};
- }
- my @pseudo_flavors = split / /, $output;
- return \@pseudo_flavors;
- }
- # Returns the reference of the package information belonging to a package
- # name from the package info array. The sub will die if there is no such
- # package listed.
- # A package listed more than one time, e.g. several subpackages, will
- # always return the first subpackage. It is taken care of the update of the
- # other subpackages via the ports Makefile, so we should only need this first
- # one.
- sub get_pkg_info {
- my ($info_array, $pkg_name) = @_;
- foreach my $info (@$info_array) {
- my $info_pkg_name = $info->{pkg};
- $info_pkg_name =~ s/$regex_subpkg$regex_flavor//g;
- return $info if ($info_pkg_name eq $pkg_name);
- }
- die "ERROR: Internal error, package information not consistent.\n";
- }
- # Tries to find the newest port from directories listed in $PORTSDIR_PATH
- # by comparing the output from make show=FULLPKGNAME.
- sub find_newer_ports_dir {
- my $info = shift;
- my $port = $info->{category} . '/' . $info->{port};
- $port .= '/' . $info->{subdir} if ($info->{subdir} ne '');
- return ($port, $info->{portdir})
- if ((defined $info->{portdir}) && ($info->{portdir} ne ''));
- my @port_locations = split /:/, $portsdir_path;
- # Take a shortcut here if we got only one location
- if (scalar @port_locations == 1) {
- $info->{portdir} = "$portsdir_path/$port";
- return ($port, $info->{portdir});
- }
- my @versions = ();
- foreach my $dir (@port_locations) {
- my $port_dir = "$dir/$port";
- my $version = get_ports_version($port_dir);
- push @versions, $version;
- }
- my $highest
- = reduce { get_higher_version($a, $b) <= 0 ? $a : $b } @versions;
- my $idx = 1;
- $idx = grep
- { defined($versions[$_])
- && ($versions[$_] eq $highest) ? $_ : -1 } 0..$#versions
- if (scalar @versions > 1);
- $info->{portdir} = "$port_locations[$idx - 1]/$port";
- return ($port, $info->{portdir});
- }
- # (Re)builds a package and updates it.
- # The following targets for make are given: repackage, update, clean
- # All output from the build process is logged and printed if the package did
- # not build.
- sub build_pkg {
- my $info = shift;
- my $cur_dir = getcwd();
- my ($port, $port_dir) = find_newer_ports_dir($info);
- my $pid = fork();
- if (! defined $pid) {
- die "Cannot fork to build $port\n";
- }
- elsif ($pid) {
- $forked_builds{$pid} = $port;
- $jobs++;
- return;
- }
- POSIX::sigaction(&POSIX::SIGCHLD, $old_child_sigaction)
- or die "Unable to set SIGCHLD handler: $!\n";
- # Give the parent time to update the forked_builds hash
- sleep 1;
- # Create the command that will build the package
- my $cmd = 'env ';
- $cmd .= 'MAKE_JOBS=' . $info->{jobs} . ' ' if ($info->{jobs});
- $cmd .= "FLAVOR=\"$info->{flavor}\" " if ($info->{flavor} ne '');
- $cmd .= "SUBPACKAGE=$info->{subpkg} " if ($info->{subpkg} ne '');
- $cmd .= 'make build';
- # chdir into the ports directory
- chdir $port_dir or die "Unable to change to \"$port_dir\"\n";
- print STDOUT "Building $info->{pkg}\n";
- my (@log, $logfile, $logfilename);
- if (defined $logdir) {
- my $tmp = $info->{port};
- $tmp .= "_$info->{subdir}" if ($info->{subdir} ne '');
- $tmp =~ s/\//_/g;
- $logfilename = "$logdir/$tmp.log";
- open($logfile, '>>', $logfilename)
- or die "Unable to create log file for $info->{pkg}: $!\n";
- }
- open(my $in, '-|', "$cmd 2>&1")
- or die qq{Unable to make "$port"\n};
- while (<$in>) {
- exit 1 if (/^Detected loop/);
- print STDOUT $_ unless (($verbose < 2) || ($num_jobs > 1));
- print $logfile $_ if (defined $logfile);
- push @log, ($_);
- }
- close($in);
- my $build_result = $?;
- my $update_result = 1;
- if ($build_result == 0) {
- $cmd = '';
- $cmd .= $sudo if ($sudo_make_clean);
- $cmd .= ' env FORCE_UPDATE=Yes CLEANDEPENDS=No ';
- $cmd .= "FLAVOR=\"$info->{flavor}\" "
- if ($info->{flavor} ne '');
- $cmd .= "SUBPACKAGE=$info->{subpkg} "
- if ($info->{subpkg} ne '');
- $cmd .= ' make repackage update clean';
- open($in, '-|', "$cmd 2>&1")
- or die "Unable to update \"$port\"\n";
- while (<$in>) {
- print STDOUT $_
- unless (($verbose < 2) || ($num_jobs > 1));
- print $logfile $_ if (defined $logfile);
- push @log, ($_);
- }
- close($in);
- $update_result = $?;
- }
- close($logfile) if (defined $logfile);
- if ($build_result != 0 || $update_result != 0) {
- # Something went wrong. We list the output of the make process
- # and kill the update process
- if (!defined $logdir) {
- foreach my $line (@log) {
- print STDOUT $line;
- }
- }
- else {
- print STDERR "Failed: See log in '$logfilename'\n";
- }
- print STDERR "Aborting build process...\n";
- exit 3;
- }
- chdir $cur_dir;
- exit 0;
- }
- sub set_parallel_build {
- my $info = shift;
- my $cur_dir = getcwd;
- my ($port, $port_dir) = find_newer_ports_dir($info);
- chdir $port_dir or die qq{Unable to change to "$port_dir"\n};
- open(my $in, '-|', 'make', 'show=DPB_PROPERTIES')
- or die qq{Unable to determine parallel build info for "$port"\n};
- my $parallel = grep { /parallel/x } (<$in>);
- close($in);
- chdir $cur_dir;
- $info->{jobs} = ($parallel) ? $make_jobs : 1;
- return $info->{jobs};
- }
- # Creates the dependencies list for a given port from the port information
- # hash
- sub create_dependencies_list {
- my $info = shift;
- my $cur_dir = getcwd;
- my @dep_list = ();
- # Create the command that will give us the dependencies list
- my $cmd = 'env ';
- $cmd .= "FLAVOR=\"$info->{flavor}\" " if ($info->{flavor} ne '');
- $cmd .= "SUBPACKAGE=$info->{subpkg} " if ($info->{subpkg} ne '');
- $cmd .= 'make build-dir-depends';
- # chdir into the ports directory
- my ($port, $port_dir) = find_newer_ports_dir($info);
- chdir $port_dir or die "Unable to change to \"$port_dir\"\n";
- open(my $in, '-|', "$cmd")
- or die "Unable to get dependencies for \"$port\"\n";
- while (<$in>) {
- chomp;
- push @dep_list, ($_);
- }
- close($in);
- if ($? != 0) {
- die "Unable to gather information for $port\n";
- }
- # Add the port itself to the list
- $port = "$info->{category}/$info->{port}";
- $port .= "/$info->{subdir}" if ($info->{subdir} ne '');
- $port .= ",$info->{subpkg}" if ($info->{subpkg} ne '');
- # Add the flavors in raw form
- if ($info->{flavor} ne '') {
- $port .= ',';
- my $flavs = $info->{flavor};
- $flavs =~ s/ /,/g;
- $port .= $flavs;
- }
- #push @dep_list, ("$port $port");
- chdir $cur_dir;
- return \@dep_list;
- }
- # Creates a single hash with all dependant packages from the array created
- # via create_dependencies_list
- sub create_dependencies_hash {
- my $dep_list = shift;
- my %dep_hash;
- foreach my $pkg (@$dep_list) {
- my $key = $pkg;
- $key =~ s/^.* //g;
- $key =~ s/$regex_subpkg$regex_flavor//g;
- $dep_hash{$key} = 1;
- }
- return \%dep_hash;
- }
- # Returns true if a package with a given name is in the package list array
- # The function will ignore flavors and subpackages.
- sub is_pkg_in_list {
- my ($pkg_name, $pkgs) = @_;
- foreach my $p (@$pkgs) {
- $p =~ s/$regex_subpkg$regex_flavor//g;
- return 1 if ($p eq $pkg_name);
- }
- return 0;
- }
- # Returns 1 if one of the dependencies from the arguments pkg info is currently
- # build by a child
- sub can_pkg_be_build {
- my $pkg = shift;
- my $pkgs = $pkg->{deppkgs};
- my @pkg_list = keys %$pkgs;
- foreach my $pid (keys %forked_builds) {
- my $build_pkg = $forked_builds{$pid};
- return 1 if (is_pkg_in_list($build_pkg, \@pkg_list));
- }
- return 0;
- }
- # Prints out the usage information on STDERR
- sub usage {
- print STDERR "USAGE: autoupdate.pl [options]\n";
- print STDERR "OPTIONS:\n"
- . " -v|--verbose\t\tSet verbosity level (1..5)\n"
- . " -f|--outdated\t\tUse file as output from the "
- . "pkg_outdated script\n"
- . " -j|--jobs\t\tTry to build packages in parallel\n"
- . " \t\t\tIf the filename is '-' the input will be\n"
- . " \t\t\tread from stdin\n"
- . " -a|--alwaysupdate\tBuild all packages, even those\n"
- . "\t\t\tflagged as always update\n"
- . " -h|--help\t\tShow this help\n";
- exit ($show_help ? 0 : 1);
- }
- ##############################################################################
- # Read the command line params
- my $result = GetOptions("v|verbose=i" => \$verbose,
- "f|outdated=s" => \$out_of_date,
- "j|jobs=i" => \$num_jobs,
- "a|alwaysupdate" => \$always_update,
- "h|help" => \$show_help);
- usage() if ($show_help != 0 || ! $result || $num_jobs < 1);
- $out_of_date = File::Spec->rel2abs($out_of_date) if (defined($out_of_date));
- my $basedir = $FindBin::Bin;
- chdir($basedir);
- # Fill the hash with the allowed variables
- my %valid_vars = ( 'logging', 1,
- 'make_jobs', 1,
- 'sudo_make_clean', 1,
- 'pseudo_flavors', 1 );
- # Read the config and set everything up
- my $config = read_rc_file(\%valid_vars);
- setup_logging($config);
- $make_jobs = $config->{make_jobs} if $config->{make_jobs};
- setup_lookup_hash($config->{pseudo_flavors}, \%pseudo_flavors,
- 'pseudo flavors');
- $sudo_make_clean = $config->{sudo_make_clean}
- if (defined $config->{sudo_make_clean});
- print STDOUT "Reading package list scheduled for update...\n";
- my $package_list = read_update_package_list($out_of_date);
- print STDOUT "Got " . @$package_list . " packages for update\n";
- print STDOUT "Gathering package information...\n";
- my $port_info = create_package_information($package_list);
- print STDOUT "Merging dependencies...\n";
- my @dep_list = ();
- my @package_dep_list = ();
- # Get the dep list of each port
- foreach my $info (@$port_info) {
- my $deps = $info->{dependencies};
- push @dep_list, @$deps
- }
- print STDOUT "Create dependencies...\n";
- my ($fh, $file, $pipe);
- ($fh, $file) = tempfile(UNLINK => 1);
- open($pipe, '|-', "tsort -r >$file") or die "ERROR: Unable to spawn tsort\n";
- foreach my $dep_entry (@dep_list) {
- # Zap the flavors and the subpackages for our dep list
- $dep_entry =~ s/$regex_subpkg$regex_flavor//g;
- print "$dep_entry\n" unless ($verbose < 5);
- print $pipe "$dep_entry\n";
- }
- close $pipe;
- die "ERROR: Internal error\n" if ($? != 0);
- # Read the result
- while (<$fh>) {
- my $pkg = $_;
- chomp $pkg;
- push @package_dep_list, ($pkg);
- }
- close $fh;
- print STDOUT "Got " . @package_dep_list . " packages as dependencies\n"
- unless ($verbose < 1);
- print STDOUT "Removing unneeded packages...\n";
- # Remove all packages from @package_dep_list that are not in the @package_list
- # array, so that only those packages may be build that are in need.
- my @pkg_list = ();
- foreach my $pkg_name (@package_dep_list) {
- if (is_pkg_in_list($pkg_name, $package_list) == 1) {
- push @pkg_list, ($pkg_name);
- }
- }
- if ($verbose > 1) {
- print STDOUT "Got " . @pkg_list . " packages for building:\n";
- foreach my $pkg_name (@pkg_list) {
- print STDOUT $pkg_name . "\n";
- }
- }
- POSIX::sigaction(
- &POSIX::SIGCHLD,
- POSIX::SigAction->new(
- \&REAPER, POSIX::SigSet->new(), &POSIX::SA_NODEFER
- ),
- $old_child_sigaction
- ) or die "Unable to set SIGCHLD signal handler: $!\n";
- # Build all the packages. Packages that are still unknown to us are ignored.
- foreach my $pkg_name (@pkg_list) {
- reap();
- # Get the package information from the info array
- my $info = get_pkg_info($port_info, $pkg_name);
- if (not defined $info) {
- print STDERR "Unable to retrieve package information for "
- . $pkg_name . "\n";
- next;
- }
- my $wait_for_dep_notice = 0;
- # XXX Here we must check if the package depends on a currently build
- # package too.
- while (($jobs >= $num_jobs || can_pkg_be_build($info)) && !$abort) {
- if ($wait_for_dep_notice == 0 && !can_pkg_be_build($info)) {
- print STDOUT "Waiting for dependant package to finish "
- . "building ($pkg_name)\n"
- unless ($verbose < 2);
- $wait_for_dep_notice++;
- }
- reap();
- sleep 1;
- }
- last if ($abort != 0);
- build_pkg($info);
- }
- # Wait for all childs to finish
- while (scalar (keys %forked_builds)) {
- sleep 1;
- reap();
- }
- POSIX::sigaction(
- &POSIX::SIGCHLD,
- $old_child_sigaction
- ) or warn "Unable to set SIGCHLD signal handler: $!\n";
- die "Abort requested by child\n" if ($abort != 0);
- print STDOUT "Done.\n";
- exit 0;
|