#!/usr/bin/perl -w

# Script to handle building KDE from Subversion.  All of the configuration is
# stored in the file ~/.kdesvn-buildrc.
#
# Please also see the documentation that should be included with this program,
# from the kdesdk/doc/scripts/kdesvn-build directory.
#
# Copyright © 2003 - 2008 Michael Pyne. <michael.pyne@kdemail.net>
# Home page: http://kdesvn-build.kde.org/
#
# You may use, alter, and redistribute this software under the terms
# of the GNU General Public License, v2 (or any later version).

#Pod documentation:

=head1 NAME

=over

=item B<kdesvn-build> - automate the KDE build process from its source repository

=back

=head1 SYNOPSIS

=over

=item B<kdesvn-build> I<[options]...> I<[modules]...>

=back

=head1 DESCRIPTION

The B<kdesvn-build> script is used to automate the download, build,
and install process for KDE (using Subversion).

It is recommended that you first setup a F<.kdesvn-buildrc> file
in your home directory. Please refer to the B<kdesvn-build> help file
in KDE help for information on how to write F<.kdesvn-buildrc>,
or consult the sample file which should have been included
with this program.  If you don't setup a F<.kdesvn-buildrc>, a
default set of options will be used, and a few modules will be
built by default.

After setting up F<.kdesvn-buildrc>, you can run this program from
either the command-line or from cron. It will automatically
download the modules from Subversion, create the build
system, and configure and make the modules you tell it to.
You can use this program to install KDE as well,
if you are building KDE for a single user. Note that B<kdesvn-build>
will try to install the modules by default.

If you DO specify module names on the command line, then your settings will
still be read from F<.kdesvn-buildrc>, but the script will try to build and
install the given modules in the order given on the command line.

kdesvn-build reads options in the following order:

=over

=item 1. From the command line.

=item 2. From the file F<kdesvn-buildrc> in the current directory.  Note that
         the file is not a hidden file.

=item 3. From the file F<~/.kdesvn-buildrc>.

=item 4. From a set of internal options.

=back

This utility is part of the KDE Software Development Kit.

=head1 OPTIONS

=over

=item B<--quiet>,  B<-q>

With this switch kdesvn-build will only output a general overview of the build
process.  Progress output is still displayed if available.

=item B<--really-quiet>

With this switch only warnings and errors will be output.

=item B<--verbose>,  B<-v>

Be very detailed in what is going on, and what actions kdesvn-build is taking.
Only B<--debug> is more detailed.

=item B<--no-svn>

Skip contacting the Subversion server.

=item B<--no-build>

Skip the build process.

=item B<--no-install>

Don't automatically install after build.

=item B<--svn-only>

Update from Subversion only (Identical to B<--no-build> at this point).

=item B<--build-only>

Build only, do not perform updates or install.

=item B<--rc-file=E<lt>filenameE<gt>>

Read configuration from filename instead of default.

=item B<--no-async>

Do not perform the source update and build in parallel.  With this enabled
the entire source update will be performed before the build will start.  This
will take longer overall.  You should only need to use this if you encounter
bugs with the default (asynchronous) behavior.

=item B<--debug>

Activates debug mode.

=item B<--pretend>,  B<-p>

Do not contact the Subversion server, run make, or create / delete files
and directories. Instead, output what the script would have done.

=item B<--nice=E<lt>valueE<gt>>

Allow you to run the script with a lower priority. The default value is
10 (lower priority by 10 steps).

=item B<--prefix=/kde/path>

This option is a shortcut to change the setting for kdedir from the
command line. It implies B<--reconfigure>.

=item B<--color>

Add color to the output.

=item B<--no-color>

Remove color from the output.

=item B<--resume-from=E<lt>pkgE<gt>>

Starts building from the given package, without performing the Subversion
update.

=item B<--revision=E<lt>revE<gt>>, B<-r=E<lt>revE<gt>>

Forces update to revision <rev> from Subversion.

=item B<--refresh-build>

Start the build from scratch.  This means that the build directory for the
module B<will be deleted> before make -f Makefile.cvs or cmake is run again.
You can use B<--recreate-configure> to do the same thing without deleting the
module build directory for KDE 3 modules.  KDE 4 modules have no separate
configure command, use the B<--reconfigure> option to get the same effect.

=item B<--reconfigure>

Run configure again, but don't clean the build directory or recreate the
configure script.  For KDE 4 modules, this option runs cmake again without
deleting the build directory.

=item B<--recreate-configure>

Run make -f Makefile.cvs again to redo the configure script.  The build
directory is not deleted.  This command is ignored for KDE 4 modules, where
it is not applicable.

=item B<--no-rebuild-on-fail>

Do not try to rebuild a module from scratch if it failed building.  Normally
kdesvn-build will try progressively harder to build the module before giving
up.   This option is always enabled for KDE 4 modules, where the build system
is accurate enough that the rebuilding behavior is not necessary.

=item B<--build-system-only>

Create the build infrastructure, but don't actually perform the build.

=item B<--install>

Try to install the packages passed on the command line, or all packages in
F<~/.kdesvn-buildrc> that don't have manual-build set. Building and
Subversion updates are not performed.

=item B<--E<lt>optionE<gt>=>

Any unrecognized options are added to the global configuration, overriding
any value that may exist.

For example, B<--svn-server=http://path.to.svn.server/> would change the
setting of the global B<svn-server> option for this instance of kdesvn-build.

=item B<--E<lt>moduleE<gt>,E<lt>optionE<gt>=>

Likewise, allow you to override any module specific option from the
command line.

Example: B<--kdelibs,use-unsermake=false> would disable unsermake for the
kdelibs module.

=item B<--help>

Display the help and exit.

=item B<--author>

Output the author(s)'s name.

=item B<--version>

Output the program version.

=back

=head1 EXAMPLES

=over

=item B<kdesvn-build>

=item B<kdesvn-build> I<--no-svn kdelibs>

=item B<kdesvn-bulid> I<--refresh-build> I<kdebase>

=back

=head1 BUGS

Since kdesvn-build doesn't generally save information related to the build and
prior settings, you may need to manually re-run kdesvn-build with a flag like
B<--recreate-configure> if you change some options, including B<use-unsermake>.

Please use KDE bugzilla at http://bugs.kde.org for information and
reporting bugs.

=head1 SEE ALSO

You can find additional information at B<kdesvn-build> home page,
F<http://kdesvn-build.kde.org/>, or using kdesvn-build
docbook documentation, using the help kioslave, F<help:/kdesvn-build>.

=head1 AUTHOR

Michael Pyne <michael.pyne@kdemail.net>

Man page written by:
Carlos Leonhard Woelz <carlos.woelz@kdemail.net>

=cut

use strict;
use warnings;
use Fcntl;    # For sysopen constants
use POSIX qw(strftime :sys_wait_h);
use File::Find; # For our lndir reimplementation.
use File::Basename;
use Sys::Hostname;
use IO::Handle;
use Errno qw(:POSIX);

# Debugging level constants.
use constant {
    DEBUG   => 0,
    WHISPER => 1,
    INFO    => 2,
    NOTE    => 3,
    WARNING => 4,
    ERROR   => 5,
};

# IPC message types
use constant {
    IPC_MODULE_SUCCESS => 1, # Used for a successful svn checkout
    IPC_MODULE_FAILURE => 2, # Used for a failed svn checkout
    IPC_MODULE_SKIPPED => 3, # Used for a skipped svn checkout (i.e. build anyways)

    # One of these messages should be the first message placed on the queue.
    IPC_ALL_SKIPPED    => 4, # Used to indicate a skipped update process (i.e. build anyways)
    IPC_ALL_FAILURE    => 5, # Used to indicate a major update failure (don't build)
    IPC_ALL_UPDATING   => 6, # Informational message, feel free to start the build.
};

my $versionNum = '1.6.1';

# Some global variables
# Remember kids, global variables are evil!  I only get to do this
# because I'm an adult and you're not! :-P
# Options that start with a # will replace values with the same name,
# if the option is actually set.
my %package_opts = (
  'global' => {
    "apidox"              => "",
    "apply-qt-patches"    => "",
    "async"               => 1,
    "binpath"             => $ENV{'PATH'},
    "branch"              => "",
    "build-dir"           => "build",
    "build-system-only"   => "",
    "checkout-only"       => "",
    "cmake-options"       => "",
    "configure-flags"     => "--enable-debug",
    "colorful-output"     => 1, # Use color by default.
    "cxxflags"            => "-pipe",
    "debug"               => "",
    "debug-level"         => INFO,
    "dest-dir"            => '${MODULE}', # single quotes used on purpose!
    "disable-agent-check" => 0,   # If true we don't check on ssh-agent
    "disable-snapshot"    => 0,   # If true, don't check for module snapshots.
    "do-not-compile"      => "",
    "email-address"       => "",
    "email-on-compile-error" => "",
    "install-after-build" => 1, # Default to true
    "inst-apps"           => "",
    "kdedir"              => "$ENV{HOME}/kde",
    "kde-languages"       => "",
    "libpath"             => "",
    "log-dir"             => "log",
    "make-install-prefix" => "",  # Some people need sudo
    "make-options"        => "-j2",
    "manual-build"        => "",
    "manual-update"       => "",
    "module-base-path"    => "",  # Used for tags and branches
    "niceness"            => "10",
    "no-svn"              => "",
    "no-rebuild-on-fail"  => "",
    "override-url"        => "",
    "prefix"              => "", # Override installation prefix.
    "pretend"             => "",
    "qtdir"               => "$ENV{HOME}/kdesvn/build/qt-copy",
    "reconfigure"         => "",
    "recreate-configure"  => "",
    "refresh-build"       => "",
    "remove-after-install"=> "none", # { none, builddir, all }
    "revision"            => 0,
    "set-env"             => { }, # Hash of environment vars to set
    "source-dir"          => "$ENV{HOME}/kdesvn",
    "stop-on-failure"     => "",
    "svn-server"          => "svn://anonsvn.kde.org/home/kde",
    "tag"                 => "",
    "tarball-host"        => "kdesvn-build.kde.org",            # Undocumented
    "tarball-path"        => "/other",                          # Undocumented
    "tarball-revision-script" => "/other/module-revision.php",  # Undocumented
    "unsermake-options"   => "--compile-jobs=2 -p",
    "unsermake-path"      => "unsermake",
    "use-stable-kde"      => 0,   # Controls whether to default to 3.5 or 4.
    "use-unsermake"       => "1", # Default to true now, we may need a blacklist
  }
);

# Base class for IPC interaction. Should have most of the functionality, with
# the actual bits of reading and writing left to subclasses.
{
    package BaseIPC;

    sub new
    {
        my $class = shift;

        # Must bless a hash ref since subclasses expect it.
        my $ref = {};
        $ref->{'residue'} = ''; # Define this for later.

        return bless $ref, $class;
    }

    sub notifyUpdateSuccess
    {
        my $self = shift;
        my ($module, $msg) = @_;

        $self->sendIPCMessage(main::IPC_MODULE_SUCCESS, "$module,$msg");
    }

    # Sends an IPC message along with some IPC type information.
    #
    # First parameter is the IPC type to send.
    # Second parameter is the actual message.
    # All remaining parameters are sent to the object's sendMessage()
    #  procedure.
    sub sendIPCMessage
    {
        # Use shift for these to empty @_ of the parameters.
        my $self = shift;
        my $ipcType = shift;
        my $msg = shift;

        my $encodedMsg = pack("l! a*", $ipcType, $msg);
        return $self->sendMessage("$encodedMsg\n", @_);
    }

    # Static class function to unpack a message.
    #
    # First parameter is the message.
    # Second parameter is a reference to a scalar to store the result in.
    #
    # Returns the IPC message type.
    sub unpackMsg
    {
        my ($msg, $outBuffer) = @_;
        my $returnType;

        ($returnType, $$outBuffer) = unpack("l! a*", $msg);

        return $returnType;
    }

    # Receives an IPC message and decodes it into the message and its
    # associated type information.
    #
    # First parameter is a *reference* to a scalar to hold the message contents.
    # All remaining parameters are passed to the underlying receiveMessage()
    #  procedure.
    #
    # Returns the IPC type, or undef on failure.
    sub receiveIPCMessage
    {
        my $self = shift;
        my $outBuffer = shift;

        # Check if we still have data left over from last read, and if it
        # contains a full message.
        if ($self->{'residue'} =~ /\n/)
        {
            my ($first, $remainder) = split(/\n/, $self->{'residue'}, 2);
            $self->{'residue'} = defined $remainder ? $remainder : '';

            return unpackMsg($first, $outBuffer);
        }

        # Read in messages enough to get to the message separator (\n)
        my $msg = '';
        while($msg !~ /\n/) {
            my $msgFragment = $self->receiveMessage(@_);
            $msg .= $msgFragment if defined $msgFragment;

            last unless defined $msgFragment;
        }

        return undef if not defined $msg or $msg eq '';

        # We may have residue still if we had a partial husk of a message, so
        # append to the residue before breaking up the message.  We assume a
        # newline separates the messages.
        $msg = $self->{'residue'} . $msg;
        my ($first, $remainder) = split(/\n/, $msg, 2);

        # Save rest for later.
        $self->{'residue'} = defined $remainder ? $remainder : '';

        return unpackMsg($first, $outBuffer);
    }

    # These must be reimplemented.  They must be able to handle scalars without
    # any extra frills.
    #
    # sendMessage should accept one parameter (the message to send) and return
    # true on success, or false on failure.  $! should hold the error information
    # if false is returned.
    sub sendMessage { die "Unimplemented.\n"; }

    # receiveMessage should return a message received from the other side, or
    # undef for EOF or error.  On error, $! should be set to hold the error
    # information.
    sub receiveMessage { die "Unimplemented.\n" }

    # Should be reimplemented if default does not apply.
    sub supportsConcurrency
    {
        return 0;
    }
}

# IPC class that uses pipes for communication.  Basically requires
# forking two children in order to communicate with.  Assumes that the two
# children are the update process and a monitor process which keeps the update
# going and informs us (the build process) of the status when we're ready to
# hear about it.
{
    package PipeIPC;

    our(@ISA);
    @ISA = qw(BaseIPC);

    sub new
    {
        my $class = shift;
        my $self = $class->SUPER::new;

        # Define file handles.
        $self->{$_} = new IO::Handle foreach qw/fromMon toMon fromSvn toBuild/;

        if (not pipe($self->{'fromSvn'}, $self->{'toMon'})or
            not pipe($self->{'fromMon'}, $self->{'toBuild'}))
        {
            return undef;
        }

        return bless $self, $class;
    }

    # Must override to send to correct filehandle.
    sub notifyUpdateSuccess
    {
        my $self = shift;
        my ($module, $msg) = @_;

        $self->sendIPCMessage(main::IPC_MODULE_SUCCESS, "$module,$msg", 'toMon');
    }

    # Closes the given list of filehandle ids.
    sub closeFilehandles
    {
        my $self = shift;
        my @fhs = @_;

        for my $fh (@fhs) {
            close $self->{$fh};
            $self->{$fh} = 0;
        }
    }

    # Call this to let the object know it will be the update process.
    sub setUpdater
    {
        my $self = shift;
        $self->closeFilehandles(qw/fromSvn fromMon toBuild/);
    }

    sub setBuilder
    {
        my $self = shift;
        $self->closeFilehandles(qw/fromSvn toMon toBuild/);
    }

    sub setMonitor
    {
        my $self = shift;
        $self->closeFilehandles(qw/toMon fromMon/);
    }

    sub supportsConcurrency
    {
        return 1;
    }

    # First parameter is the ipc Type of the message to send.
    # Second parameter is the module name (or other message).
    # Third parameter is the file handle id to send on.
    sub sendMessage
    {
        my $self = shift;
        my ($msg, $fh) = @_;

        return syswrite ($self->{$fh}, $msg);
    }

    # Override of sendIPCMessage to specify which filehandle to send to.
    sub sendIPCMessage
    {
        my $self = shift;
        push @_, 'toMon'; # Add filehandle to args.

        return $self->SUPER::sendIPCMessage(@_);
    }

    # Used by monitor process, so no message encoding or decoding required.
    sub sendToBuilder
    {
        my ($self, $msg) = @_;
        return $self->sendMessage($msg, 'toBuild');
    }

    # First parameter is a reference to the output buffer.
    # Second parameter is the id of the filehandle to read from.
    sub receiveMessage
    {
        my $self = shift;
        my $fh = shift;
        my $value;

        undef $!; # Clear error marker
        my $result = sysread ($self->{$fh}, $value, 256);

        return undef if not $result;
        return $value;
    }

    # Override of receiveIPCMessage to specify which filehandle to receive from.
    sub receiveIPCMessage
    {
        my $self = shift;
        push @_, 'fromMon'; # Add filehandle to args.

        return $self->SUPER::receiveIPCMessage(@_);
    }

    # Used by monitor process, so no message encoding or decoding required.
    sub receiveFromUpdater
    {
        my $self = shift;
        return $self->receiveMessage('fromSvn');
    }
}

# Dummy IPC module in case SysVIPC doesn't work.
{
    package NullIPC;

    our @ISA = qw(BaseIPC);

    sub new
    {
        my $class = shift;
        my $self = $class->SUPER::new;

        $self->{'msgList'} = []; # List of messages.
        return bless $self, $class; # OOP in Perl is so completely retarded
    }

    sub sendMessage
    {
        my $self = shift;
        my $msg = shift;

        push @{$self->{'msgList'}}, $msg;
        return 1;
    }

    sub receiveMessage
    {
        my $self = shift;

        return undef unless scalar @{$self->{'msgList'}} > 0;

        return shift @{$self->{'msgList'}};
    }
}

# This is a hash since Perl doesn't have a "in" keyword.
my %ignore_list;  # List of packages to refuse to include in the build list.

# update and build are lists since they support an ordering, which can't be
# guaranteed using a hash unless I want a custom sort function (which isn't
# necessarily a horrible way to go, I just chose to do it this way.
my @update_list;  # List of modules to update/checkout.
my @build_list;   # List of modules to build.

# Dictionary of lists of failed modules, keyed by the name of the operation
# that caused the failure (e.g. build).  Note that output_failed_module_lists
# uses the key name to display text to the user so it should describe the
# actual category of failure.  You should also add the key name to
# output_failed_module_lists since it uses its own sorted list.
my @fail_display_order = qw/build update install/;
my %fail_lists = (
    'build'   => [ ],
    'install' => [ ],
    'update'  => [ ],
);

my $install_flag; # True if we're in install mode.
my $BUILD_ID;     # Used by logging subsystem to create a unique log dir.
my $LOG_DATE;     # Used by logging subsystem to create logs in same dir.
my @rcfiles = ("./kdesvn-buildrc", "$ENV{HOME}/.kdesvn-buildrc");
my $rcfile; # the file that was used; set by read_options
my @screen_log;

# Colors
my ($RED, $GREEN, $YELLOW, $NORMAL, $BOLD) = ("") x 5;

# Subroutine definitions

# I swear Perl must be the only language where the docs tell you to use a
# constant that you'll never find exported without some module from CPAN.
use constant PRIO_PROCESS => 0;

# I'm lazy and would rather write in shorthand for the colors.  This sub
# allows me to do so. Put it right up top to stifle Perl warnings.
sub clr($)
{
    my $str = shift;

    $str =~ s/g\[/$GREEN/g;
    $str =~ s/]/$NORMAL/g;
    $str =~ s/y\[/$YELLOW/g;
    $str =~ s/r\[/$RED/g;
    $str =~ s/b\[/$BOLD/g;

    return $str;
}

# Subroutine which returns true if pretend mode is on.  Uses the prototype
# feature so you don't need the parentheses to use it.
sub pretending()
{
    return get_option('global', 'pretend');
}

# Subroutine which returns true if debug mode is on.  Uses the prototype
# feature so you don't need the parentheses to use it.
sub debugging()
{
    return get_option('global', 'debug-level') <= DEBUG;
}

# The next few subroutines are used to print output at different importance
# levels to allow for e.g. quiet switches, or verbose switches.  The levels are,
# from least to most important:
# debug, whisper, info (default), note (quiet), warning (very-quiet), and error.
#
# You can also use the pretend output subroutine, which is emitted if, and only
# if pretend mode is enabled.
#
# clr is automatically run on the input for all of those functions.
# Also, the terminal color is automatically reset to normal as well so you don't
# need to manually add the ] to reset.

# Subroutine used to actually display the data, calls clr on each entry first.
sub print_clr(@)
{
    print clr $_ foreach (@_);
    print clr "]\n";

    push(@screen_log, join("\n", @_));
}

sub debug(@)
{
    print_clr @_ if debugging;
}

sub whisper(@)
{
    print_clr @_ if get_option('global', 'debug-level') <= WHISPER;
}

sub info(@)
{
    print_clr @_ if get_option('global', 'debug-level') <= INFO;
}

sub note(@)
{
    print_clr @_ if get_option('global', 'debug-level') <= NOTE;
}

sub warning(@)
{
    print_clr @_ if get_option('global', 'debug-level') <= WARNING;
}

sub error(@)
{
    print STDERR (clr $_) foreach (@_);
    print STDERR (clr "]\n");
}

sub pretend(@)
{
    print_clr @_ if pretending;
}

# Subroutine to handle removing the lock file upon receiving a signal
sub quit_handler
{
    note "Signal received, terminating.";
    finish(5);
}

# Subroutine that returns the path of a file used to output the results of the
# build process.  It accepts one parameter, which changes the kind of file
# returned.  If the parameter is set to 'existing', then the file returned is
# the latest file that exists, or undef if no log has been created yet.    All
# other values will return the name if a file that does not yet exist.
#
# All files will be stored in the log directory.
sub get_output_file
{
    my $logdir;
    my $mode;
    $mode = shift or $mode = '';
    my $fname;

    debug "get_output_file in mode $mode";

    if ($mode eq 'existing')
    {
        # There's two ways of finding the old file.  Searching backwards with
        # valid combinations of the date and build id, or just reading in the
        # name from a known file or location.  Since the latter option is much
        # easier, that's what I'm going with.  Note that this depends on the
        # latest symlink being in place.
        $logdir = get_subdir_path ('global', 'log-dir');
        $fname = "$logdir/latest/build-status";

        debug "Old build status file is $fname";

        # The _ at the end returns the cached file stats to avoid multiple
        # stat() calls.
        return "" if not -e $fname or not -r _;

        return $fname;
    }

    # This call must follow the test above, because it changes the 'latest'
    # symlink leading to failures later.
    $logdir = get_log_dir('global');

    $fname = "$logdir/build-status";
    debug "Build status file is $fname";

    return $fname;
}

# Subroutine that returns the path of a file used to output the log messages
#
# All files will be stored in the log directory.
sub get_screen_log_file
{
    return get_log_dir('global') . "/build-log";
}

# Subroutine to retrieve a subdirectory path for the given module.
# First parameter is the name of the module, and the second
# parameter is the option key (e.g. build-dir or log-dir).
sub get_subdir_path
{
    my $module = shift;
    my $option = shift;
    my $dir = get_option($module, $option);

    # If build-dir starts with a slash, it is an absolute path.
    return $dir if $dir =~ /^\//;

    # If it starts with a tilde, expand it out.
    if ($dir =~ /^~/)
    {
        $dir =~ s/^~/$ENV{'HOME'}/;
    }
    else
    {
        # Relative directory, tack it on to the end of $kdesvn.
        my $kdesvndir = get_kdesvn_dir();
        $dir = "$kdesvndir/$dir";
    }

    return $dir;
}

# Subroutine to return the name of the destination directory for the checkout
# and build routines.  Based on the dest-dir option.  The return value will be
# relative to the src/build dir.  The user may use the '$MODULE' or '${MODULE}'
# sequences, which will be replaced by the name of the module in question.
#
# The first parameter should be the module name.
sub get_dest_dir
{
    my $module = shift;
    my $dest_dir = get_option($module, 'dest-dir');

    $dest_dir =~ s/(\${MODULE})|(\$MODULE\b)/$module/g;

    return $dest_dir;
}

# This function returns true if the give module uses CMake.  If the user has
# specified a choice, we use the user's choice regardless for now.  If no user
# choice is given, auto-detect based on searching for filenames.
#
# First parameter: Module to check.
# Return: True (non-zero) if user has chosen cmake or CMake support is detected,
#         False (0, undef) if user does not want cmake or no CMake support is detected.
sub module_uses_cmake
{
    my $module = shift;

    my $srcdir = get_fullpath($module, 'source');
    return 1 if -e "$srcdir/CMakeLists.txt";

    # Kind of a hack but it beats inventing a sequence of checks that happens
    # to get it right: just hardcode in the module that is the exception to the
    # rule.

    return 1 if $module =~ /^l10n-kde4\/?/;

    # No CMakeLists.txt found, if the directory existed don't use CMake,
    # otherwise use the stable-kde option to determine.

    return 0 if -e $srcdir;

    return not get_option($module, 'use-stable-kde');
}

# Convenience subroutine to get the source root dir.
sub get_kdesvn_dir
{
    my $module = shift;
    $module = 'global' unless defined $module;

    return get_option ($module, 'source-dir');
}

# Function to work around a Perl language limitation.
# First parameter is the list to search.
# Second parameter is the value to search for.
# Returns true if the value is in the list
sub list_has(\@$)
{
    my ($list_ref, $value) = @_;
    return scalar grep ($_ eq $value, @{$list_ref});
}

# Subroutine to return the branch prefix. i.e. the part before the branch name
# and module name.
#
# The first parameter is the module in question.
# The second parameter should be 'branches' if we're dealing with a branch or
#     'tags' if we're dealing with a tag.
#
# Ex: 'kdelibs'  => 'branches/KDE'
#     'kdevelop' => 'branches/kdevelop'
sub branch_prefix
{
    my $module = shift;
    my $type = shift;

    # These modules seem to have their own subdir in /tags.
    my @tag_components = qw/arts koffice amarok kst qt taglib/;

    # The map call adds the kde prefix to the module names because I don't feel
    # like typing them all in. kdevelop and konstruct are special cases.
    my @kde_module_list = ((map {'kde' . $_} qw/accessibility
            addons admin artwork base bindings edu games graphics libs
            multimedia network pim pimlibs plasma-addons sdk toys utils webdev/), 'kdevelop',
            'konstruct', 'kdevplatform');

    # If the user already has the module in the form KDE/foo, it's already
    # done.
    return "$type/KDE" if $module =~ /^KDE\//;

    # KDE proper modules seem to use this pattern.
    return "$type/KDE" if list_has(@kde_module_list, $module);

    # KDE extragear / playground modules use this pattern
    return "$type" if has_base_module($module);

    # If we doing a tag just return 'tags' because the next part is the actual
    # tag name, which is added by the caller, unless the module has its own
    # subdirectory in /tags.
    return "$type" if $type eq 'tags' and not list_has(@tag_components, $module);

    # Everything else.
    return "$type/$module";
}

# Subroutine to return a module URL for a module using the 'branch' option.
# First parameter is the module in question.
# Second parameter is the type ('tags' or 'branches')
sub handle_branch_tag_option
{
    my ($module, $type) = @_;
    my $svn_server = get_option($module, 'svn-server');
    my $branch = branch_prefix($module, $type);
    my $branchname = get_option($module, 'tag');

    if($type eq 'branches')
    {
        $branchname = get_option($module, 'branch');
    }

    # qt-copy is referred to as qt in svn when dealing with branches and tags.
    $branch = branch_prefix('qt', $type) if $module eq 'qt-copy';

    # Remove trailing slashes.
    $svn_server =~ s/\/*$//;

    # Remove KDE/ prefix for module name.
    $module = moduleBaseName($module);

    # Phonon has a slightly different branching scheme which includes the backends, therefore
    # they are up one directory from normal.
    # i.e. /branches/phonon/4.2/{backends,phonon}
    return "$svn_server/$branch/$branchname" if $module eq 'phonon';

    return "$svn_server/$branch/$branchname/$module";
}

# Subroutine to return the appropriate SVN URL for a given module, based on
# the user settings.  For example, 'kdelibs' -> https://svn.kde.org/home/kde/trunk/KDE/kdelibs
sub svn_module_url
{
    my $module = shift;
    my $svn_server = get_option($module, 'svn-server');
    my $branch = get_option($module, 'module-base-path');

    # Allow user to override normal processing of the module in a few ways,
    # to make it easier to still be able to use kdesvn-build even when I
    # can't be there to manually update every little special case.
    if(get_option($module, 'override-url'))
    {
        return get_option($module, 'override-url');
    }

    if(get_option($module, 'tag'))
    {
        return handle_branch_tag_option($module, 'tags');
    }

    # Note we check for 'trunk', not default_module_branch().  We handle 'trunk' in the
    # rest of the code path, any branch (even if default) should be handled in
    # handle_branch_tag_option().
    if(get_option($module, 'branch') and
       get_option($module, 'branch') ne 'trunk')
    {
        return handle_branch_tag_option($module, 'branches');
    }

    if($module eq 'phonon')
    {
        # As of this time there is no phonon trunk as it's not a real module, only a
        # figment of kdesvn-build's imagination.
        error <<EOF;
You are attempting to build r[b[phonon] from KDE trunk.  However phonon is
developed in the y[b[kdesupport] module, not in its own module.

If you intend to build the stable phonon you should add the appropriate
g[b[branch] option in your rc file, i.e.

module phonon
    branch 4.2
end module

If you intend to build the latest phonon you must build it from kdesupport, remembering
not to disable that module in your g[b[cmake-options].
EOF
    }

    # We can't use get_option($module) after this if we have to trim the module
    # name.
    $module = moduleBaseName($module);

    # The following modules are in /trunk, not /trunk/KDE.  There are others,
    # but there are the important ones.  The hash is associated with the value
    # 1 so that we can do a boolean test by looking up the module name.
    my @non_trunk_modules = qw(extragear kdesupport koffice icecream kde-common
        playground qt-copy valgrind KDE kdereview www l10n-kde3 l10n-kde4);

    my $module_root = $module;
    $module_root =~ s/\/.*//; # Remove everything after the first slash

    if (not $branch)
    {
        $branch = 'trunk/KDE';
        $branch = 'trunk' if list_has(@non_trunk_modules, $module_root);
    }

    $branch =~ s/^\/*//; # Eliminate / at beginning of string.
    $branch =~ s/\/*$//; # Likewise at the end.

    # Remove trailing slashes.
    $svn_server =~ s/\/*$//;

    return "$svn_server/$branch/$module";
}

# Returns true if the Net::HTTP module is available.
BEGIN {
    my $Net_HTTP_available = undef;

    sub has_Net_HTTP
    {
        return $Net_HTTP_available if defined $Net_HTTP_available;

        eval {
            require Net::HTTP;
            $Net_HTTP_available = 1;
        } or do {
            error " y[*] Can't open y[b[Net::HTTP] module, skipping check for module snapshot.";
            debug "Error was $@";
            $Net_HTTP_available = 0;
        };

        return $Net_HTTP_available;
    }
}

# Convenience subroutine to return the build directory for a module. Use
# this instead of get_subdir_path because this special-cases modules for you,
# such as qt-copy.
#
# In previous versions of kdesvn-build, qt-copy may have been built in the
# source directory.  Even for Qt 3, kdesvn-build could build in the build
# directory by using the use-qt-builddir-hack option.  But as of now, the
# option is always true, so even Qt 3 is built in the build directory, using
# the hack automatically if necessary.
#
# The returned value does not include the module name at the end (as the build
# path on disk doesn't always use the module name given in the .kdesvn-buildrc),
# so be sure to add on the module name if needed.
sub get_build_dir
{
    my $module = shift;

    return get_subdir_path($module, 'build-dir');
}

# Subroutine to return a list of the different log directories that are used
# by the different modules in the script.
sub get_all_log_directories
{
    my @module_list = keys %package_opts;
    my %log_dict;

    # A hash is used to track directories to avoid duplicate entries.
    unshift @module_list, "global";
    $log_dict{get_subdir_path($_, 'log-dir')} = 1 foreach @module_list;

    debug "Log directories are ", join (", ", keys %log_dict);
    return keys %log_dict;
}

# Subroutine to determine the build id for this invocation of the script.  The
# idea of a build id is that we want to be able to run the script more than
# once in a day and still retain each set of logs.  So if we run the script
# more than once in a day, we need to increment the build id so we have a
# unique value.  This subroutine sets the global variable $BUILD_ID and
# $LOG_DATE for use by the logging subroutines.
sub setup_logging_subsystem
{
    my $min_build_id = "00";
    my $date = strftime "%F", localtime; # ISO 8601 date
    my @log_dirs = get_all_log_directories();

    for (@log_dirs)
    {
        my $id = "01";
        $id++ while -e "$_/$date-$id";

        # We need to use a string comparison operator to keep
        # the magic in the ++ operator.
        $min_build_id = $id if $id gt $min_build_id;
    }

    $LOG_DATE = $date;
    $BUILD_ID = $min_build_id;
}

# Convienience subroutine to return the log directory for a module.
# It also creates the directory and manages the 'latest' symlink.
#
# Returns undef on an error, or the name of the directory otherwise.
sub get_log_dir
{
    my $module = shift;
    my $logbase = get_subdir_path($module, 'log-dir');
    my $logpath = "$logbase/$LOG_DATE-$BUILD_ID/$module";

    $logpath = "$logbase/$LOG_DATE-$BUILD_ID" if $module eq 'global';

    return $logpath if pretending;

    debug "Log directory for $module is $logpath";

    if (not -e $logpath and not super_mkdir($logpath))
    {
        error "Unable to create log directory r[$logpath]";
        return undef;
    }

    # Add symlink to the directory if it doesn't already point to the right
    # location.  This operation should be atomic in async mode since the
    # update thread should create the log dir before it is needed by the build
    # thread (which will not have to unlink/symlink).
    if(-l "$logbase/latest" and readlink("$logbase/latest") ne "$LOG_DATE-$BUILD_ID")
    {
        unlink("$logbase/latest") if -l "$logbase/latest";
        symlink("$LOG_DATE-$BUILD_ID", "$logbase/latest");
    }

    return $logpath;
}

# This function returns true if the given option doesn't make sense with the
# given module.
# blacklisted($module, $option)
sub blacklisted
{
    my ($module, $option) = @_;

    # Known to not work.
    my @unsermake_ban_list = qw/valgrind kde-common qt-copy kdebindings/;

    return list_has(@unsermake_ban_list, $module) if ($option eq 'use-unsermake');
    return 0;
}

# This subroutine returns an option value for a given module.  Some
# globals can't be overridden by a module's choice.  If so, the
# module's choice will be ignored, and a warning will be issued.
#
# Option names are case-sensitive!
#
# First parameter: Name of module
# Second paramenter: Name of option
sub get_option
{
    my $module = shift;
    my $option = shift;
    my $global_opts = $package_opts{'global'};
    my @lockedOpts = qw(pretend disable-agent-check);

    # These options should not apply to qt-copy when specified as part of
    # globals by default.  Only options which might reasonably be used in the
    # global section have been included here.
    my @qtCopyOverrides = qw(branch configure-flags tag);

    # These options can't override globals
    if (list_has(@lockedOpts, $option) or $module eq 'global')
    {
        return ${$global_opts}{"#$option"} if exists ${$global_opts}{"#$option"};
        return ${$global_opts}{$option};
    }

    # Don't even try this
    return 0 if blacklisted($module, $option);

    my $ref = $package_opts{$module};

    # Check for a sticky option
    return $$ref{"#$option"} if exists $$ref{"#$option"};

    # Next in order of precedence
    if (defined ${$global_opts}{"#$option"} and not
        ($module eq 'qt-copy' and list_has(@qtCopyOverrides, $option)))
    {
        return ${$global_opts}{"#$option"};
    }

    # No sticky options left.
    # Configure flags, cmake options, and CXXFLAGS are appended to the global
    # option
    if (($module ne 'qt-copy' && $option eq 'configure-flags')
        || $option eq 'cxxflags' || $option eq 'cmake-options')
    {
        my $value = ${$global_opts}{$option};

        if(defined $$ref{$option})
        {
            my $modvalue = $$ref{$option};
            $value .= " $modvalue";
        }

        return $value;
    }

    # qt-copy should already have default options set, no need to check
    # specifically for it.  We still may need to prevent returning a set global
    # option when the default is unset.

    # Everything else overrides the global, unless of course it's not set.
    # If we're reading for global options, we're pretty much done.
    return $$ref{$option} if defined $$ref{$option};

    # Some global options would probably make no sense applied to qt-copy.
    return 0 if $module eq 'qt-copy' and list_has(@qtCopyOverrides, $option);

    return ${$global_opts}{$option};
}

# Subroutine used to handle the checkout-only option.  It handles
# updating subdirectories of an already-checked-out module.
#
# This function can throw an exception in the event of a update failure.
#
# First parameter is the module.
# All remaining parameters are subdirectories to check out.
#
# Returns the number of files changed by the update, or undef if unable to
# be determined.
sub update_module_subdirectories
{
    my $module = shift;
    my $numChanged = 0;

    # If we have elements in @path, download them now
    for my $dir (@_)
    {
        info "\tUpdating g[$dir]";

        my $count = run_svn($module, "svn-up-$dir", [ 'svn', 'up', $dir ]);
        $numChanged = undef unless defined $count;
        $numChanged += $count if defined $numChanged;
    }

    return $numChanged;
}

# Returns true if a module has a base component to their name (e.g. KDE/,
# extragear/, or playground).  Note that modules that aren't in trunk/KDE
# don't necessary meet this criteria (e.g. kdereview is a module itself).
sub has_base_module
{
    my $module = shift;

    return $module =~ /^(extragear|playground|KDE)(\/[^\/]+)?$/;
}

# Subroutine to return the directory that a module will be stored in.
# NOTE: The return value is a hash. The key 'module' will return the final
# module name, the key 'path' will return the full path to the module. The
# key 'fullpath' will return their concatenation.
# For example, with $module == 'KDE/kdelibs', and no change in the dest-dir
# option, you'd get something like:
# {
#   'path'     => '/home/user/kdesvn/KDE',
#   'module'   => 'kdelibs',
#   'fullpath' => '/home/user/kdesvn/KDE/kdelibs'
# }
# If dest-dir were changed to e.g. extragear-multimedia, you'd get:
# {
#   'path'     => '/home/user/kdesvn',
#   'module'   => 'extragear-multimedia',
#   'fullpath' => '/home/user/kdesvn/extragear-multimedia'
# }
# First parameter is the module.
# Second parameter is either source or build.
sub get_module_path_dir
{
    my $module = shift;
    my $type = shift;
    my $destdir = get_dest_dir($module);
    my $srcbase = get_kdesvn_dir();
    $srcbase = get_build_dir($module) if $type eq 'build';

    my $combined = "$srcbase/$destdir";

    # Remove dup //
    $combined =~ s/\/+/\//;

    my @parts = split(/\//, $combined);
    my %result = ();
    $result{'module'} = pop @parts;
    $result{'path'} = join('/', @parts);
    $result{'fullpath'} = "$result{path}/$result{module}";

    return %result;
}

sub get_fullpath
{
    my ($module, $type) = @_;
    my %pathinfo = get_module_path_dir($module, $type);

    return $pathinfo{'fullpath'};
}

# This subroutine downloads the specified file from a host, and saves it to the
# given filename.
#
# First parameter: Hostname of the server (i.e. kdesvn-build.kde.org)
# Second parameter: Path of the file on the host (i.e. /files/blah.tbz2)
# Third parameter: Filename to save as (i.e. $ENV{HOME}/blah.tbz2)
# Fourth parameter: Reference to hash used to record if a redirection occurred,
#                   and how many redirections have already been attempted.
#                   See download_file()
# Return value is 0 for failure, non-zero for success.
sub download_http_file
{
    my ($host, $path, $filename, $info) = @_;

    return 0 unless has_Net_HTTP();
    my $conn = Net::HTTP->new (Host => $host);

    if (not defined $conn)
    {
        error "Unable to connect to snapshot host: r[$@]";

        return 0;
    }

    debug "Checking for $path";

    # Send connection request
    $conn->write_request('GET' => "$path",
                         'User-Agent' => "Mozilla/5.0 (compatible; kdesvn-build $versionNum)",
                         );

    my ($code, $msg, %h) = $conn->read_response_headers();

    # Try to handle redirections.  We handle them all pretty much the same,
    # i.e. if the Location response is present use that, otherwise error out.
    while (int $code / 100 == 3)
    {
        $info->{'redir_count'}++;
        $conn->close(); # Error or not, we're done with this connection.

        if (not $h{'Location'})
        {
            error "Unable to download file r[$path], ambiguous redirection.";
            return 0;
        }

        my $destination = $h{'Location'};
        $destination =~ s/^Location:\s*//;
        $info->{'redirection'} = $destination;
        return 0;
    }

    if (200 != $code)
    {
        error "Unable to download file r[$path]:\n\tr[b[$msg]";

        $conn->close();
        return 0;
    }

    open OUTPUT, ">$filename" or do {
        error "Unable to open output file for r[$path] download.";
        error "\tMessage: b[r[$@]";

        $conn->close();
        return 0;
    };

    my ($buf, $result);
    while (1)
    {
        $result = $conn->read_entity_body($buf, 2048);
        if (not defined $result)
        {
            error "Error downloading from host: r[$!]";

            $conn->close();
            close OUTPUT;
            safe_unlink($filename);

            return 0;
        }

        last unless $result; # Break loop if end-of-data
        print OUTPUT $buf;   # Print downloaded data to file.
    }

    close OUTPUT;
    $conn->close();

    return 1;
}

# This subroutine downloads the file pointed to by the URL given in the first
# parameter, saving to the given filename.  (FILENAME, not directory).
#
# First parameter: FTP Host. (i.e. ftp.kde.org)
# Second parameter: Path to file, including file name (i.e. /pub/unstable/foo.tbz2)
# Third parameter: Filename to save as (i.e. $ENV{HOME}/blah.tbz2)
# Return value is 0 for failure, non-zero for success.
sub download_ftp_file
{
    my ($host, $path, $filename) = @_;

    # Detect Net::FTP.
    my $ftp;
    eval {
        require Net::FTP;
    } or do {
        error " y[*] Can't open y[b[Net::FTP] module, skipping check of g[ftp.kde.org].";
        debug "Error was $@";
        return 0;
    };

    $ftp = new Net::FTP($host, Timeout => 30);

    if (not $ftp)
    {
        error "Unable to connect to snapshot host $host: r[$@]";
        return 0;
    }

    if (not $ftp->login())
    {
        error "Connection refused to FTP host r[$host], skipping snapshot.";
        error "FTP response: $ftp->message";
        return 0;
    }

    $ftp->binary(); # Switch to binary mode.

    # Check if file exists.
    my $size = $ftp->size($path);
    if (not $size or $size <= 0)
    {
        $ftp->quit();
        return 0;
    }

    if (not defined $ftp->get($path, $filename))
    {
        # Download failed.
        error "Unable to download snapshot from r[$host].";
        return 0;
    }

    $ftp->quit();
    return 1;
}

# This subroutine downloads the file pointed to by the URL given in the first
# parameter, saving to the given filename.  (FILENAME, not directory). HTTP
# and FTP are supported, depending on if the required Net::HTTP and Net::FTP
# modules are available.
#
# First parameter: URL of link to download (i.e. http://kdesvn-build.kde.org/foo.tbz2)
# Second parameter: Filename to save as (i.e. $ENV{HOME}/blah.tbz2)
# Return value is 0 for failure, non-zero for success.
sub download_file
{
    my $url = shift;
    my $filename = shift;

    if (pretending)
    {
        pretend "Would have downloaded g[$url]\n\tto g[$filename]";
        return 1;
    }

    my ($protocol, $host, $path);
    my $info = { 'redir_count' => 0, 'redirection' => '' };

    while ($info->{'redir_count'} < 5)
    {
        ($protocol, $host, $path) = ($url =~ m{^([^:]+)://([^/]+)(/.*)$});
        if (not defined $url or not defined $host or not defined $path)
        {
            error "Trying to download file from invalid URL: r[$url]";
            return 0;
        }

        $info->{'redirection'} = '';

        # Not sure if https works but no harm in letting it try.
        if ($protocol =~ /^https?$/)
        {
            whisper "Downloading g[$path] from g[$url]";
            my $result = download_http_file($host, $path, $filename, $info);

            if (not $result and $info->{'redirection'})
            {
                # Try again at new URL.
                $url = $info->{'redirection'};
                whisper "Redirecting to y[$url]";
                next;
            }
            else
            {
                return $result;
            }
        }
        elsif ($protocol eq 'ftp')
        {
            whisper "Downloading g[$path] from g[$url]";
            return download_ftp_file($host, $path, $filename);
        }
        else
        {
            error "Trying to download file ($url), but";
            error "\tthe r[$protocol] protocol is unsupported.";
            return 0;
        }
    }

    return 0;
}

# This subroutine is used to try and download a Subversion checkout snapshot
# for a given module.  For trunk modules, the KDE FTP repository is checked
# first.  For other modules or if not available from KDE FTP, the host
# set by the tarball-host option (default kdesvn-build.kde.org) is checked.
#
# If available the snapshot is downloaded and installed into the normal
# location for a kdesvn-build source checkout and already switched into the
# correct svn-server setting.
#
# The first parameter is the module to download.
# Return value is boolean true if successful, false otherwise.
sub install_module_snapshot
{
    my $module = shift;

    # Don't bother with snapshot if the user has their own URL picked out.
    return 0 if (get_option($module, 'override-url'));

    my $moduleName = moduleBaseName($module); # KDE/kdelibs -> kdelibs

    # The branch for the module is the tag option if specified, otherwise the
    # branch option if specified.  Otherwise the default branch for the
    # module is selected (usually trunk, perhaps 3.5)
    my $branch = get_option($module, 'tag');
    $branch = get_option($module, 'branch') if not $branch;
    $branch = default_module_branch($module) if not $branch;

    my ($filename, $url, $dirName);

    # If a trunk module, try to obtain from KDE FTP first.
    if ($branch eq 'trunk')
    {
        $filename = "$moduleName-svn.tar.bz2";
        #$url = "ftp://ftp.kde.org/pub/kde/unstable/snapshots/$filename";
        $url = "http://download.kde.org/download.php?url=unstable/snapshots/$moduleName-svn.tar.bz2";
        $dirName = "$moduleName";

        if (download_module_snapshot($module, $filename, $url, $dirName))
        {
            return 1; # We're done.
        }
    }

    # No snapshot from KDE FTP, try the normal source.
    my $revision = tarball_snapshot_revision($module);
    my $tarballHost = get_option($module, 'tarball-host');
    my $snapshotPath = get_option($module, 'tarball-path');

    if ($revision <= 0)
    {
        return 0; # No snapshot is available.
    }

    $filename = "$moduleName-$branch-svn-r$revision.tar.bz2";
    $url = "http://$tarballHost$snapshotPath/$filename";
    $dirName = "$moduleName-$branch-svn-r$revision";

    return download_module_snapshot($module, $filename, $url, $dirName);
}

# This subroutine tries to download a Subversion checkout snapshot of a given
# module (and it actually handles the downloading).  If the download succeeds,
# this function will automatically arrange the extracted module to fit the
# normal kdesvn-build source layout, and take the necessary steps to restore
# the snapshot to a state as if it had just been checked out from Subversion.
#
# The finalized Subversion checked will then have svn up run in order to
# complete the checkout.
#
# If a failure occurs, the function will try to clean up after itself.  So,
# no snapshot tarball or module directory should be present if 0 is returned.
#
# The first parameter is the module to download.
# The second parameter is the filename to use.
# The third parameter is the URL to download.
# The fourth parameter is the final directory name from the extracted tarball.
#
# Return value is boolean true if successful, false otherwise.
sub download_module_snapshot
{
    my ($module, $filename, $url, $dirName) = @_;
    my $moduleName = moduleBaseName($module);

    # We are in either the source directory or $srcdir/KDE so moduleBaseName
    # is always the right directory name.

    if (pretending)
    {
        pretend "Would have downloaded snapshot for g[$module], from";
        pretend "\tb[g[$url]";
        return 1;
    }

    info "Downloading snapshot for g[$module]";

    if (not download_file($url, $filename))
    {
        error "Unable to download snapshot for module r[$module]";
        return 0;
    }

    info "\tDownload complete for g[$module], completing snapshot.";

    # Now extract the newly downloaded file. First decompress it. (Don't
    # use the j flag as it doesn't appear to be portable.
    my $result = safe_system('bunzip2', $filename);
    if($result) { # failure
        error "Unable to decompress snapshot for r[$module]: $@";

        # Clean up the probably defective snapshot.
        safe_unlink($filename);

        return 0;
    }

    # The file doesn't end in .bz2 anymore.
    $filename =~ s/\.bz2$//;

    # Extract the file.
    $result = safe_system("tar", "xf", $filename);
    my $savedError = $@; # Make sure safe_unlink doesn't overwrite.

    # Snapshot file is no longer necessary.
    safe_unlink($filename);

    if($result) { # failure
        error "Unable to extract snapshot for r[$module]: $savedError";

        # Remove any created portions of the module tree.
        safe_rmtree($dirName);

        return 0;
    }

    whisper "\tExtracted directory for g[$module]";

    # The extracted directory is possibly of a weird form, move it to the
    # correct name (just $moduleName);
    if($dirName ne $moduleName and not safe_rename($dirName, $moduleName)) {
        error "Unable to move directory for r[$module] into place: r[$!]";

        # Remove any created portions of the module tree.
        safe_rmtree($dirName);
        safe_rmtree($moduleName);

        return 0;
    }

    whisper "\tg[$module] snapshot is in place.";

    # Module in place, now prepare it for checkout.
    p_chdir($moduleName);

    # Switch svn host to proper host.
    my $svnHost = get_option($module, 'svn-server');
    my $curSvnHost = `svn info | grep ^URL`;
    $curSvnHost =~ s/^URL:\s*//;
    $curSvnHost =~ s/\/home\/kde.*$/\/home\/kde/; # Remove stuff after /home/kde
    chomp $curSvnHost;

    info "\tFinalizing Subversion information for g[$module]";

    # Set svn snapshot to update from the correct svn host instead of the default
    # anonsvn (if different).
    if($svnHost ne $curSvnHost)
    {
        $result = log_command($module, 'svn-snapshot-switch',
            ['svn', 'switch', '--relocate', $curSvnHost, $svnHost]);

        if($result)
        {
            error "Unable to switch snapshot Subversion source to the KDE Subversion server!";
            error "\tr[$!]";

            # Remove any created portions of the module tree.
            p_chdir("..");
            safe_rmtree($moduleName);

            return 0;
        }
    }
    else
    {
        debug "Skipping svn switch step, it is unnecessary.";
    }

    whisper "\tRestoring module file layout to normal.";

    # Finally, restore the file structure.
    $result = log_command($module, 'svn-restore-checkout',
        [ 'svn', 'revert', '-R', '.' ]);

    if($result)
    {
        error "Unable to restore standard Subversion layout!";

        # Remove any created portions of the module tree.
        p_chdir("..");
        safe_rmtree($moduleName);

        return 0;
    }

    info "Snapshot checkout complete, g[$module] at snapshot's revision.";
    info "\nCompleting checkout by updating g[$module]";

    $result = log_command($module, 'svn-first-up', ['svn', 'up']);
    if($result)
    {
        error "Unable to update module r[$module] to latest code.  The module";
        error "is correctly checked-out however, so it may be possible to try";
        error "again later.";

        # Don't delete anything here, we're close enough to being good that
        # kdesvn-build should be able to fix this later.
        return 1;
    }

    info "Checkout complete for g[$module]!";

    return 1;
}

# This subroutine is responsible for stripping the KDE/ part from the beginning
# of modules that were entered by the user like "KDE/kdelibs" instead of the
# normal "kdelibs".  That way you can search for kdelibs without having to
# strip KDE/ everywhere.
sub moduleBaseName
{
    my $module = shift;
    $module =~ s/^KDE\///;

    return $module;
}

# This subroutine contacts a remote web server (defined by the tarball-host
# option) and calls a php script to determine if there is a tarball snapshot
# available for the given module and branch.  The revision number of the
# snapshot is returned.  0 is returned if there is no revision available or
# some other error occurred.
#
# First parameter: Module to query for.
sub tarball_snapshot_revision
{
    my $module = shift;
    my $tarballHost = get_option($module, "tarball-host");
    my $revisionScript = get_option($module, "tarball-revision-script");
    my $branch = get_option($module, 'branch');
    my $moduleName = moduleBaseName($module);

    whisper "Checking if snapshot available for g[$module]";

    return 0 unless has_Net_HTTP();

    if (get_option($module, 'override-url'))
    {
        whisper "\tb[override-url] in effect for g[$module], snapshot not available.";
        return 0;
    }

    $branch = default_module_branch($module) if not $branch; # Use default value.
    debug "Looking up revision number for g[$module-$branch] from g[$tarballHost$revisionScript]";

    my $conn = Net::HTTP->new ('Host' => $tarballHost);
    if (not defined $conn)
    {
        error "Unable to connect to $tarballHost, snapshot check aborted.";
        error "\tError message: r[$@]";
        return 0;
    }

    # Send connection request
    $conn->write_request('GET' => "$revisionScript?module=$moduleName&branch=$branch",
                         'User-Agent' => "Mozilla/5.0 (compatible; kdesvn-build $versionNum)",
                         );

    my ($code, $msg, %h) = $conn->read_response_headers();

    # Require HTTP success code.
    if (200 != $code)
    {
        debug "Server returned $code $msg";
        whisper "Error received from server: r[$code $msg]";
        return 0;
    }

    my ($buf, $result, $revision);
    $revision = '';

    while (1)
    {
        $result = $conn->read_entity_body($buf, 2048);
        if (not defined $result)
        {
            whisper "Error reading revision number from server: r[$!]";
            return 0;
        }

        last unless $result;
        $revision .= $buf;
    }

    # Response received should be in the form "Revision: <number>".  Strip down
    # to the number part.
    chomp $revision;
    ($result) = ($revision =~ /^Revision:\s*(\d+)/);

    $conn->close;

    if ($result and $result > 0)
    {
        whisper "g[$module] is g[available] for rapid download.";
        return $result;
    }
    else
    {
        whisper "g[$module] is y[not available] for rapid download.";
        return 0;
    }
}

# Checkout a module that has not been checked out before, along with any
# subdirectories the user desires.
#
# This function will throw an exception in the event of a failure to update.
#
# The first parameter is the module to checkout (including extragear and
# playground modules).
# All remaining parameters are subdirectories of the module to checkout.
#
# Returns number of files affected, or undef.
sub checkout_module_path
{
    my ($module, @path) = @_;
    my %pathinfo = get_module_path_dir($module, 'source');
    my @args;

    if (not -e $pathinfo{'path'} and not super_mkdir($pathinfo{'path'}))
    {
        die clr "Unable to create path r[$pathinfo{path}]!";
    }

    p_chdir ($pathinfo{'path'});

    # First let's see if we have a snapshot checkout available.  If so, it
    # would probably be quicker to use it.
    # TODO: Make this work when using the checkout-only option.
    if (not pretending and not scalar @path and not get_option($module, 'disable-snapshot'))
    {
        if(install_module_snapshot($module))
        {
            whisper "Snapshot checkout successful!";
            # Success, no need to continue on this route.
            return undef;
        }
    }

    push @args, ('svn', 'co', '--non-interactive');
    push @args, '-N' if scalar @path; # Tells svn to only update the base dir
    push @args, svn_module_url($module);
    push @args, $pathinfo{'module'};

    note "Checking out g[$module]";
    my $count = run_svn($module, 'svn-co', \@args);

    p_chdir ($pathinfo{'module'}) if scalar @path;

    my $count2 = update_module_subdirectories($module, @path);

    return $count + $count2 if defined $count and defined $count2;
    return undef;
}

# Update a module that has already been checked out, along with any
# subdirectories the user desires.
#
# This function will throw an exception in the event of an update failure.
#
# The first parameter is the module to checkout (including extragear and
# playground modules).
# All remaining parameters are subdirectories of the module to checkout.
sub update_module_path
{
    my ($module, @path) = @_;
    my $fullpath = get_fullpath($module, 'source');
    my @args;

    p_chdir ($fullpath);

    eval { plugin_update_module_path($module); };

    push @args, ('svn', 'up', '--non-interactive');
    push @args, '-N' if scalar @path;

    note "Updating g[$module]";

    my $count = eval { run_svn($module, 'svn-up', \@args); };

    if($@) # Update failed, try svn cleanup.
    {
        info "\tUpdate failed, trying a cleanup.";
        my $result = safe_system('svn', 'cleanup');

        die clr "Unable to update r[$module]" if $result;

        info "\tCleanup complete.";

        # Now try again.
        $count = run_svn($module, 'svn-up-2', \@args);
    }

    # If the admin dir exists and is a soft link, remove it so that svn can
    # update it if need be.  The link will automatically be re-created later
    # in the process if necessary by the build functions.
    unlink ("$fullpath/admin") if -l "$fullpath/admin";

    my $count2 = update_module_subdirectories($module, @path);

    return $count + $count2 if defined $count and defined $count2;
    return undef;
}

# The function checks whether subversion already has an ssl acceptance
# notification for svn.kde.org, and if it's doesn't, installs one.
# Problems: First off, installing any kind of "accept this ssl cert without
# user's active consent" kind of sucks.  Second, this function is very
# specific to the various signature algorithms used by svn, so it could break
# in the future.  But there's not a better way to skip warnings about svn.kde.org
# until the site has a valid ssl certificate.
#
# Accepts no arguments, has no return value.
sub install_missing_ssl_signature
{
    my $sig_dir  = "$ENV{HOME}/.subversion/auth/svn.ssl.server";
    my $sig_file = "ec08b331e2e6cabccb6c3e17a85e28ce";

    debug "Checking $sig_dir/$sig_file for KDE SSL signature.";

    if (-e "$sig_dir/$sig_file")
    {
        debug "KDE SSL Signature file present.";
        return;
    }

    debug "No KDE SSL Signature found.";
    return if pretending;

    # Now we're definitely installing, let the user know.
    warning "Installing b[y[KDE SSL signature] for Subversion.  This is to avoid";
    warning "Subversion warnings about KDE's self-signed SSL certificate for svn.kde.org";

    # Make sure the directory is created.
    if(not super_mkdir($sig_dir))
    {
        error "Unable to create r[Subversion signature] directory!";
        error "$!";

        return;
    }

    my $sig_data =
'K 10
ascii_cert
V 1216
MIIDijCCAvOgAwIBAgIJAO9Ca3rOVtgrMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYDVQQGE\
wJERTEQMA4GA1UECBMHQmF2YXJpYTESMBAGA1UEBxMJTnVlcm5iZXJnMREwDwYDVQQKEw\
hLREUgZS5WLjEMMAoGA1UECxMDU1ZOMRQwEgYDVQQDEwtzdm4ua2RlLm9yZzEfMB0GCSq\
GSIb3DQEJARYQc3lzYWRtaW5Aa2RlLm9yZzAeFw0wNTA1MTExMDA4MjFaFw0xNTA1MDkx\
MDA4MjFaMIGLMQswCQYDVQQGEwJERTEQMA4GA1UECBMHQmF2YXJpYTESMBAGA1UEBxMJT\
nVlcm5iZXJnMREwDwYDVQQKEwhLREUgZS5WLjEMMAoGA1UECxMDU1ZOMRQwEgYDVQQDEw\
tzdm4ua2RlLm9yZzEfMB0GCSqGSIb3DQEJARYQc3lzYWRtaW5Aa2RlLm9yZzCBnzANBgk\
qhkiG9w0BAQEFAAOBjQAwgYkCgYEA6COuBkrEcEJMhzHajKpN/StQwr/YeXIXKwtROWEt\
7evsXBNqqRe6TuUc/iVYgBuZ4umVlJ/qJ7Q8cSa8Giuk2B3ShZx/WMSC80OfGDJ4LoWm3\
uoW8h45ExAACBlhuuSSa7MkH6EXhru1SvLbAbTcSVqyTzoWxhkAb8ujy6CUxHsCAwEAAa\
OB8zCB8DAdBgNVHQ4EFgQUx2W0046HfWi1fGL1V8NlDJvnPRkwgcAGA1UdIwSBuDCBtYA\
Ux2W0046HfWi1fGL1V8NlDJvnPRmhgZGkgY4wgYsxCzAJBgNVBAYTAkRFMRAwDgYDVQQI\
EwdCYXZhcmlhMRIwEAYDVQQHEwlOdWVybmJlcmcxETAPBgNVBAoTCEtERSBlLlYuMQwwC\
gYDVQQLEwNTVk4xFDASBgNVBAMTC3N2bi5rZGUub3JnMR8wHQYJKoZIhvcNAQkBFhBzeX\
NhZG1pbkBrZGUub3JnggkA70Jres5W2CswDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQU\
FAAOBgQDjATlL2NByFDo5hhQAQdXjSYrMxil7zcpQjR+KYVizC7yK99ZsA0LYf/Qbu/pa\
oMnmKLKWeNlF8Eq7/23TeAJmjw1pKi97ZO2FJ8jvy65iBEJLRYnpJ75dvg05iugm9GZ5w\
Px6GHZmkSrteGDXgVbbSDy5exv1naqc+qEM7Ar4Xw==
K 8
failures
V 1
8
K 15
svn:realmstring
V 23
https://svn.kde.org:443
END
';

    # Remove the \<newline> parts (the gibberish should be one big long
    # line).
    $sig_data =~ s/\\\n//gm;

    if(not open SIG, ">$sig_dir/$sig_file")
    {
        error "Unable to open KDE SSL signature file!";
        error "r[$!]";

        return;
    }

    if(not print SIG $sig_data)
    {
        error "Unable to write to KDE SSL signature file!";
        error "r[$!]";
    }

    close SIG;
}

# Subroutine to run a command, optionally filtering on the output of the child
# command.
#
# First parameter is the name of the module being built (for logging purposes
#   and such).
# Second parameter is the name of the log file to use (relative to the log
#   directory).
# Third parameter is a reference to an array with the command and its
#   arguments.  i.e. ['command', 'arg1', 'arg2']
# Fourth parameter (optional) is a reference to a subroutine to have each line
#   of child output passed to.  This output is not supposed to be printed to
#   the screen by the subroutine, normally the output is only logged.  However
#   this is useful for e.g. munging out the progress of the build.
#   USEFUL: When there is no more output from the child, the callback will be
#     called with an undef string.  (Not just empty, it is also undefined).
# The return value is the shell return code, so 0 is success, and non-zero is
#   failure.
#
# NOTE: This function has a special feature.  If the command passed into the
#   argument reference is 'kdesvn-build', then log_command will, when it forks,
#   execute the subroutine named by the second parameter rather than executing
#   a child process.  The remaining arguments in the list are passed to the
#   subroutine that is called.
sub log_command
{
    my ($module, $filename, $argRef, $callbackRef) = @_;
    my $pid;
    my @command = @{$argRef};
    my $logdir = get_log_dir($module);

    debug "log_command(): Module $module, Command: ", join(' ', @command);

    if (pretending)
    {
        pretend "\tWould have run g[", join (' ', @command);
        return 0;
    }

    # Fork a child, with its stdout connected to CHILD.
    $pid = open(CHILD, '-|');
    if ($pid)
    {
        # Parent
        while (<CHILD>)
        {
            if (defined $callbackRef)
            {
                # Call callback with current output.
                &{$callbackRef}($_);
            }
            else
            {
                debug $_;
            }
        }

        close CHILD;

        # Let callback know there is no more output.
        &{$callbackRef}(undef) if defined $callbackRef;

        # If the module fails building, set an internal flag in the module
        # options with the name of the log file containing the error message.
        my $result = $?;
        set_error_logfile($module, "$filename.log") if $result;

        return $result;
    }
    else
    {
        # Child
        if (not defined $logdir or not -e $logdir)
        {
            # Error creating directory for some reason.
            error "\tLogging to std out due to failure creating log dir.";
        }

# The stdin redirection used to be commented out because it will cause
# problems for users using make-install-prefix when a password is desired, or
# when svn complains about the SSL signature.  I think I've fixed the latter,
# and I've decided that users should configure sudo to not need the password,
# or simply run sudo kdesvn-build instead of using make-install-prefix.  Now
# other commands will fail instead of hanging at the terminal.  As it stands, it can still
# be canceled using an exported env var just in case.

        open (STDIN, "</dev/null") unless exists $ENV{'KDESVN_BUILD_USE_TTY'};
        open (STDOUT, "|tee $logdir/$filename.log") or do {
            error "Error opening pipe to tee command.";
            # Don't abort, hopefully STDOUT still works.
        };

        # Make sure we log everything.  If the command is svn, it is possible
        # that the client will produce output trying to get a password, so
        # don't redirect stderr in that case.
        open (STDERR, ">&STDOUT") unless $command[0] eq 'svn';

        # Call internal function, name given by $command[1]
        if($command[0] eq 'kdesvn-build')
        {
            debug "Calling $command[1]";

            my $cmd = $command[1];
            splice (@command, 0, 2); # Remove first two elements.

            no strict 'refs'; # Disable restriction on symbolic subroutines.
            if (not &{$cmd}(@command)) # Call sub
            {
                exit EINVAL;
            }

            exit 0; # Exit child process successfully.
        }

        # External command.
        exec (@command) or do {
            my $cmd_string = join(' ', @command);
            error <<EOF;
r[b[Unable to execute "$cmd_string"]!
	$!

Please check your binpath setting (it controls the PATH used by kdesvn-build).
Currently it is set to g[$ENV{PATH}].
EOF
            # Don't use return, this is the child still!
            exit 1;
        };
    }
}

# Subroutine to mark a file as being the error log for a module.  This also
# creates a symlink in the module log directory for easy viewing.
# First parameter is the module in question.
# Second parameter is the filename in the log directory of the error log.
sub set_error_logfile
{
    my ($module, $logfile) = @_;
    my $logdir = get_log_dir($module);

    return unless $logfile;

    set_option($module, '#error-log-file', "$logdir/$logfile");
    debug "Logfile for $module is $logfile";

    # Setup symlink in the module log directory pointing to the appropriate
    # file.  Make sure to remove it first if it already exists.
    unlink("$logdir/error.log") if -l "$logdir/error.log";

    if(-e "$logdir/error.log")
    {
        # Maybe it was a regular file?
        error "r[b[ * Unable to create symlink to error log file]";
        return 0;
    }

    symlink "$logfile", "$logdir/error.log";
}

# Subroutine to run make (unsermake with KDE 3) and process the build process
# output in order to provide completion updates.  If using CMake, CMake 2.4.3
# or later is required.  This procedure takes the same arguments as
# log_command() (described here as well), except that the callback argument
# is not used.
#
# First parameter is the name of the module being built (for logging purposes
#   and such).
# Second parameter is the name of the log file to use (relative to the log
#   directory).
# Third parameter is a reference to an array with the command and its
#   arguments.  i.e. ['command', 'arg1', 'arg2']
# The return value is the shell return code, so 0 is success, and non-zero is
#   failure.
sub run_make_command
{
    my ($module, $filename, $argRef) = @_;

    my $isunsermake = ${$argRef}[0] =~ 'unsermake';

    debug "run_make_command: $module, ", join(', ', @{$argRef});

    # There are situations when we don't want (or can't get) progress output:
    # 1. Automake (i.e. not cmake, not unsermake)
    # 2. If we're not printing to a terminal.
    # 3. When we're debugging (we'd interfere with debugging output).
    if ((!module_uses_cmake($module) and !$isunsermake) or
        not -t STDERR or
        debugging)
    {
        return log_command($module, $filename, $argRef);
    }

    # Make sure -p is in the unsermake flags, it's the whole reason for using
    # this function.
    if ($isunsermake and !(grep /^(-p)|(--print-progress)$/, @{$argRef}))
    {
        # Add in front of element 1, deleting 0 elements.
        splice @{$argRef}, 1, 0, '-p';
    }

    # Setup callback function for use by log_command.
    my $last = -1;

    # w00t.  Check out the closure!  Maks would be so proud.
    my $log_command_callback = sub {
        my ($input) = shift;

        if (not defined $input)
        {
            # End of input, cleanup.
            print STDERR "\r\e[K";
        }
        else
        {
            chomp($input);

            my $percentage = '';

            if ($isunsermake)
            {
                if ($input =~ /([0-9]+)% (creating|compiling|linking)/)
                {
                    $percentage = $1;
                }
            }
            else # CMake.
            {
                if ($input =~ /^\[\s*([0-9]+)%]/)
                {
                    $percentage = $1;
                }
            }

            # Update terminal (\e[K clears to the end of line) if the
            # percentage changed.
            if ($percentage and $percentage ne $last)
            {
                print STDERR "\r$percentage% \e[K";
            }

            $last = $percentage;
        }
    };

    return log_command($module, $filename, $argRef, $log_command_callback);
}

# Subroutine to determine if the given subdirectory of a module can actually be
# built or not.  For instance, /admin can never be built, and the /kalyptus subdir
# of kdebindings can't either.
sub is_subdir_buildable
{
    my ($module, $dir) = @_;

    return 0 if $dir eq 'admin';
    return 0 if $dir eq 'kalyptus' and $module eq 'kdebindings';
    return 0 if $dir eq 'scripts' and ($module eq 'l10n-kde3' or $module eq 'l10n-kde4');
    return 1;
}

# Subroutine to return the path to the given executable based on the current
# binpath settings.  e.g. if you pass make you could get '/usr/bin/make'.  If
# the executable is not found undef is returned.
#
# This assumes that the module environment has already been updated since
# binpath doesn't exactly correspond to $ENV{'PATH'}.
sub path_to_prog
{
    my $prog = shift;
    my @paths = split(/:/, $ENV{'PATH'});

    # If it starts with a / the path is already absolute.
    return $prog if $prog =~ /^\//;

    for my $path (@paths)
    {
        return "$path/$prog" if (-x "$path/$prog");
    }

    return undef;
}

# Subroutine to delete a directory and all files and subdirectories within.
# Does nothing in pretend mode.  An analogue to "rm -rf" from Linux.
# Requires File::Find module.
#
# First parameter: Path to delete
# Returns boolean true on success, boolean false for failure.
sub safe_rmtree
{
    my $path = shift;
    my $delete_file_or_dir = sub {
        # $_ is the filename/dirname
        return if $_ eq '.' or $_ eq '..';
        if (-f $_)
        {
            unlink ($_) or die "Unable to delete $File::Find::name!";
        }
        elsif (-d $_)
        {
            rmdir ($File::Find::name)  or die "Unable to remove directory $File::Find::name: $!";
        }
    };

    if (pretending)
    {
        pretend "Would have recursively removed $path";
        return 1;
    }

    # Error out because we probably have a logic error even though it would
    # delete just fine.
    if (not -d $path)
    {
        error "Cannot recursively remove $path, as it is not a directory.";
        return 0;
    }

    eval {
        $@ = '';
        finddepth( # finddepth does a postorder traversal.
        {
            wanted => $delete_file_or_dir,
            no_chdir => 1, # We'll end up deleting directories, so prevent this.
        }, $path);
    };

    if ($@)
    {
        error "Unable to remove directory $path: $@";
        return 0;
    }

    return 1;
}

# Subroutine to run the make command with the arguments given by the passed
# list.  The first argument of the list given must be the module that we're
# making.  The second argument is the "try number", used in creating the log
# file name.
#
# Returns 0 on success, non-zero on failure (shell script style)
sub safe_make (@)
{
    my ($module, $trynumber, $apidox, @args) = @_;
    my $opts;
    my $logdir = get_log_dir($module);
    my $checkout_dirs = get_option($module, "checkout-only");
    my @dirs = split(' ', $checkout_dirs);
    my $installing = $trynumber eq 'install';
    my $make = 'make';

    if (module_uses_cmake($module) or get_option($module, 'use-unsermake'))
    {
        # Unsermake can't build apidox.
        # CMake will have a different way of doing so.

        my $tool = module_uses_cmake($module) ? 'cmake' : 'unsermake';

        if ($apidox)
        {
            warning " y[*] This module (g[$module]) uses $tool, but $tool doesn't";
            warning " y[*] support building the API documentation.  The most-often updated";
            warning " y[*] documentation is at http://www.englishbreakfastnetwork.org/";
            warning " y[*] r[Aborting APIDOX build]";

            return 0;
        }

        if (module_uses_cmake($module))
        {
            $opts = get_option($module, 'make-options');
        }
        else
        {
            $make = get_option($module, 'unsermake-path');
            $opts = get_option($module, 'unsermake-options');
        }
    }
    else
    {
        $opts = get_option($module, 'make-options');
    }

    # Convert the path to an absolute path since I've encountered a sudo that
    # is apparently unable to guess.  Maybe it's better that it doesn't guess
    # anyways from a security point-of-view.
    $make = path_to_prog($make) unless pretending;

    if(not defined $make)
    {
        # Weird, we can't find make, you'd think configure would have
        # noticed...
        error " r[b[*] Unable to find the g[make] executable!";

        # Make sure we don't bother trying again, this is a more serious
        # error.
        set_option($module, "#was-rebuilt", 1);
        return 1;
    }

    # Add make-options to the given options.
    # If we're installing, we need to strip out parallel build options.  If any
    # other options would break installation I will probably need to add a
    # separate make-install-options option. :-(
    unshift (@args, split(' ', $opts));

    if ($installing and grep(/^-j/, @args))
    {
        whisper "\tStripping parallel install instructions for g[$module]";

        # Find -j option
        for (my $i = 0; $i < scalar @args; ++$i)
        {
            if ($args[$i] =~ /^-j/)
            {
                # Found it, now decide whether to remove -jfoo option or -j
                # and the next argument.
                if ($args[$i] eq '-j')
                {
                    splice(@args, $i, 2); # Remove 2 elements at $i
                }
                else
                {
                    splice(@args, $i, 1); # Remove 1 element at $i
                }

                $i = -1; # Restart search ($i will increment to 0)
            }
        }
    }

    my $description;

    # Check if we're installing
    if($installing)
    {
        debug "Prepending install options, apidox: $apidox.";

        $description = $apidox ? "API Documentation" : clr "g[$module]";
        unshift @args, $make, $apidox ? 'install-apidox' : 'install';

        my @install_cmd = split(' ', get_option ($module, 'make-install-prefix'));
        if (@install_cmd)
        {
            # Add -S option if we're running sudo and it's not already
            # present.  This causes sudo to read the password from stdin (and
            # consequently fail instead of hanging at the terminal).
            if ($install_cmd[0] eq 'sudo' and not grep (/^-S$/, @install_cmd))
            {
                splice (@install_cmd, 1, 0, '-S'); # Add -S right after 'sudo'
            }

            unshift @args, @install_cmd;
        }

        info "\tInstalling $description.";
    }
    else
    {
        $description = "Building API Documentation";
        $description = "Compiling, attempt $trynumber" unless $apidox;

        push @args, 'apidox' if $apidox;
        unshift @args, $make;

        info "\t$description...";
    }

    push (@dirs, "") if scalar @dirs == 0;
    for my $subdir (@dirs)
    {
        # Some subdirectories shouldn't have make run within them.
        next unless is_subdir_buildable($module, $subdir);

        my $logname = "build-$trynumber";
        if ($installing)
        {
            $logname = $apidox ? 'install-apidox' : 'install';
        }

        if ($subdir ne '')
        {
            $logname = $installing ? "install-$subdir" : "build-$subdir-$trynumber";
            next if $apidox; # Don't built apidox in a subdirectory

            info $installing ? "\tInstalling " : "\tBuilding ", "subdirectory g[$subdir]";
        }

        my $builddir = get_fullpath($module, 'build') . "/$subdir";
        $builddir =~ s/\/*$//; # Remove trailing /

        p_chdir ($builddir);

        my $result = run_make_command ($module, $logname, \@args );
        return $result if $result;
    };

    return 0;
}

# This function returns the default branch for a given module.  Use this function whenever
# you have to deal with checking whether the user is using the default branch.
#
# This function handles the use-stable-kde option correctly as well.  This
# function may be called while parsing options for non-global modules, so it
# can only call get_option() for 'global' entries.
#
# First parameter: module to get default branch of.
# Returns: branch name. e.g. 'trunk', '3.5', 'work/make-it-cool'
sub default_module_branch
{
    # Add the appropriate branch to this hash for stable modules.  Any module not listed
    # here will default to 'trunk' when 'use-stable-kde' is set.
    my %branched_modules = (
        'kde-common'       => '3.5',
        'kdeaccessibility' => '3.5',
        'kdeadmin'         => '3.5',
        'kdeartwork'       => '3.5',
        'kdebase'          => '3.5',
        'kdebindings'      => '3.5',
        'kdeedu'           => '3.5',
        'kdegames'         => '3.5',
        'kdegraphics'      => '3.5',
        'kdelibs'          => '3.5',
        'kdemultimedia'    => '3.5',
        'kdenetwork'       => '3.5',
        'kdepim'           => '3.5',
        'kdesdk'           => '3.5',
        'kdetoys'          => '3.5',
        'kdeutils'         => '3.5',
        'kdevelop'         => '3.5',
        'kdewebdev'        => '3.5',
        'kdesupport'       => '3.5',
        'koffice'          => '1.5', # Last 3.x release of KOffice suite.
        'qt-copy'          => '3.3', # There is no Qt 3.5.x ;)
        'arts'             => '1.5', # arts had a different versioning scheme.
    );

    my $module = shift;

    # Handle stable branch options.
    if (get_option('global', 'use-stable-kde') and exists $branched_modules{$module})
    {
        return $branched_modules{$module};
    }

    # phonon has no trunk module, it is developed in kdesupport
    return '4.2' if $module eq 'phonon';

    # KDE 4 default options starting here.
    return 'trunk';
}

# Given a module name, this subroutine returns a hash with the default module
# options for the module.
#
# The global options must already be setup but there is no requirement for any
# module options to be available.
#
# First parameter is the module to get options for.
# Return is a hash containing the default module options, suitable for assignment
# to %package_opts.
sub default_module_options
{
    my $module = shift;

    return {
        'set-env' => { },
        'branch'  => default_module_branch($module),
    };
}

# Subroutine to add a variable to the environment, but ONLY if it
# is set. First parameter is the variable to set, the second is the
# value to give it.
sub setenv
{
    my ($var, $val) = @_;

    return unless $val;

    whisper "\tWould have set g[$var]=y[$val]." if pretending;

    $ENV{$var} = $val;
}

# Display a message to the user regarding their relative lack of
# ~/.kdesvn-buildrc, and point them to some help.  We will continue using a
# default set of options.
sub no_config_whine
{
    my $searched = join("\n    ", @rcfiles);
    my $homepage = "http://kdesvn-build.kde.org/";

    note <<"HOME";
Unable to open configuration file!
We looked for:
    $searched

kdesvn-build will continue using a default set of options.  These options may
not apply to you, so feel free to visit the kdesvn-build homepage

b[g[$homepage]

and access the kdesvn-build documentation.  Also the latest release package
will have a sample configuration file that you can edit to your liking.

HOME
}

# This subroutine assigns the appropriate options to %package_opts and the
# update and build lists to build a default set of modules.
#
# Note: Call this and you stand the risk of losing the options you're already
# set, do this only if you need to setup options for the entire list of
# default modules.
sub setup_default_modules()
{
    # kdesupport must come before other KDE modules.
    @update_list = qw(qt-copy kdesupport phonon kdelibs kdepimlibs kdebase kdeartwork
                      kdemultimedia kdepim kdeutils kdegraphics kdegames
                      kdetoys kdeedu kdenetwork kdeutils);

    @build_list = @update_list;

    whisper "Setting up to build ", join(', ', @build_list), " by default.";

    for my $i (@update_list) {
        my $options_ref = default_module_options($i);

        # Apply default option only if option not already set.  If the option
        # is here at this point it's probably user defined on the command line
        # or setup by kdesvn-build based on an option.
        for my $key (keys %{$options_ref}) {
            if (not exists $package_opts{$i}{$key}) {
                $package_opts{$i}{$key} = $options_ref->{$key};
            }
        }
    }

    # Setup default options for qt-copy
    $package_opts{'qt-copy'} = {
        'configure-flags' => '-qt-gif -no-phonon -qdbus -nomake demos -nomake examples -no-exceptions -fast',
        'apply-qt-patches' => 1,
        'branch'  => default_module_branch('qt-copy'),
        'set-env' => { },
        'make-options' => get_option('global', 'make-options'),
    };

    # kdesupport needs to be told to avoid phonon.
    set_option('kdesupport', 'cmake-options', '-DBUILD_phonon=OFF');
}

# Reads in the options from the config file and adds them to the option store.
# The first parameter is a reference to the file handle to read from.
# The second parameter is 'global' if we're reading the global section, or
# 'module' if we should expect an end module statement.
sub parse_module
{
    my ($fh, $module) = @_;
    $module = 'global' unless $module;

    # Setup default options in case user specifies only module name to get it
    # to build.
    if (not defined $package_opts{$module})
    {
        $package_opts{$module} = default_module_options($module);
    }

    # Read in each option
    while (<$fh>)
    {
        # Handle line continuation
        chomp;

        if(s/\\\s*$//)  # Replace \ followed by optional space at EOL and try again.
        {
            $_ .= <$fh>;
            redo unless eof($fh);
        }

        s/#.*$//;       # Remove comments
        next if /^\s*$/;   # Skip blank lines

        if($module eq 'global')
        {
            last if /^end\s+global/; # Stop
        }
        else
        {
            last if /^end\s+module/; # Stop
        }

        # The option is the first word, followed by the
        # flags on the rest of the line.  The interpretation
        # of the flags is dependant on the option.
        my ($option, $value) = /^\s*     # Find all spaces
                                ([-\w]+) # First match, alphanumeric, -, and _
                                # (?: ) means non-capturing group, so (.*) is $value
                                # So, skip spaces and pick up the rest of the line.
                                (?:\s+(.*))?$/x;

        $value = "" unless defined $value;

        # Simplify this.
        $value =~ s/\s+$//;
        $value =~ s/^\s+//;
        $value =~ s/\s+/ /;

        # Check for false keyword and convert it to Perl false.
        $value = 0 if lc($value) eq 'false';

        # Replace reference to global option with their value.
        # The regex basically just matches ${option-name}.  Keep the RE up
        # to date with the same one below.

        my ($sub_var_name) = ($value =~ m/\$\{([a-zA-Z0-9-]+)\}/);
        while ($sub_var_name)
        {
            my $sub_var_value = get_option('global', $sub_var_name);

            if(not $sub_var_value)
            {
                my $line_no = $.;
                warning " *\n * WARNING: $sub_var_name is not set at line y[$line_no]\n *";

                $sub_var_value = '';
            }

            debug "Substituting \${$sub_var_name} with $sub_var_value";

            $value =~ s/\${$sub_var_name}/$sub_var_value/g;

            # Replace other references as well.  Keep this RE up to date with
            # the other one.
            ($sub_var_name) = $value =~ m/\$\{([a-zA-Z0-9-]+)\}/;
        }

        # Replace tildes with home directory.
        1 while ($value =~ s"(^|:|=)~/"$1$ENV{'HOME'}/");

        set_option($module, $option, $value);
    }
}

# This subroutine reads in the settings from the user's configuration
# file.
sub read_options
{
    # The options are stored in the file $rcfile
    my $success = 0;
    my $global_opts = $package_opts{'global'};
    for my $file (@rcfiles)
    {
        if (open CONFIG, "<$file")
        {
            $success = 1;
            $rcfile = $file;
            last;
        }
    }

    if (not $success)
    {
        if(scalar @rcfiles == 1)
        {
            # This can only happen if the user uses --rc-file, if we fail to
            # load the file, we need to fail to load.
            error <<EOM;
Unable to open config file $rcfiles[0]

Script stopping here since you specified --rc-file on the command line to
load $rcfiles[0] manually.  If you wish to run the script with no configuration
file, leave the --rc-file option out of the command line.

If you want to force an empty rc file, use --rc-file /dev/null

EOM
            exit 1;
        }

        no_config_whine();
        setup_default_modules();
        return;
    }

    my ($option, $flags, $modulename);

    # Read in global settings
    while (<CONFIG>)
    {
        s/#.*$//;       # Remove comments
        s/^\s*//;       # Remove leading whitespace
        next if (/^\s*$/); # Skip blank lines

        # First command in .kdesvn-buildrc should be a global
        # options declaration, even if none are defined.
        if (not /^global\s*$/)
        {
            error "Invalid configuration file: $rcfile.";
            error "Expecting global settings section at line $.!";
            exit 1;
        }

        # Now read in each global option
        parse_module(\*CONFIG, 'global');
        last;
    }

    my $using_default = 1;

    if (exists $ENV{"COVERITY_RUN"}) {

        info "We're being run by coverity. ignoring non global options in the";
        info "config file";

        close CONFIG;
        setup_default_modules();
        return;
    }

    # Now read in module settings
    while (<CONFIG>)
    {
        s/#.*$//;       # Remove comments
        s/^\s*//;       # Remove leading whitespace
        next if (/^\s*$/); # Skip blank lines

        # Get modulename (has dash, dots, slashes, or letters/numbers)
        ($modulename) = /^module\s+([-\/\.\w]+)\s*$/;

        if (not $modulename)
        {
            warning "Invalid configuration file $rcfile!";
            warning "Expecting a start of module section at line $..";
            warning "Global settings will be retained.";

            $modulename = 'null'; # Keep reading the module section though.
        }

        # Don't build default modules if user has their own wishes.
        if ($using_default)
        {
            $using_default = 0;
            @update_list = @build_list = ( );
        }

        parse_module(\*CONFIG, $modulename);

        next if ($modulename eq 'null');

        # Done reading options, add this module to the update list
        push (@update_list, $modulename) unless exists $ignore_list{$modulename};

        # Add it to the build list, unless the build is only
        # supposed to be done manually.
        if (not get_option ($modulename, 'manual-build') and not exists $ignore_list{$modulename})
        {
            push (@build_list, $modulename);
        }
    }

    close CONFIG;

    delete $package_opts{'null'}; # Just in case.

    # If the user doesn't ask to build any modules, build a default set.
    # The good question is what exactly should be built, but oh well.
    setup_default_modules() if $using_default;
}

# Subroutine to check if the given module needs special treatment to support
# srcdir != builddir.  If this function returns true kdesvn-build will use a
# few hacks to simulate it, and will update e.g. configure paths appropriately
# as well.
#
# Only run for non-CMake modules.
sub module_needs_builddir_help
{
    my $module = shift;
    my @module_help_list = qw/kdebindings valgrind/;

    # l10n/lang needs help.
    return 1 if ($module =~ /^l10n-kde[34]\/?/);

    return list_has(@module_help_list, $module);
}

# This subroutine reads the set-env option for a given module and initializes
# the environment based on that setting.
sub setup_module_environment
{
    my $module = shift;
    my ($key, $value);

    # Let's see if the user has set env vars to be set.
    my $env_hash_ref = get_option($module, 'set-env');
    while (($key, $value) = each %{$env_hash_ref})
    {
        setenv($key, $value);
    }
}

# Subroutine to initialize some environment variable for building
# KDE from Subversion.  Change this section if a dependency changes later.
sub initialize_environment
{
    if(-t STDOUT and get_option('global', 'colorful-output'))
    {
        $RED = "\e[31m";
        $GREEN = "\e[32m";
        $YELLOW = "\e[33m";
        $NORMAL = "\e[0m";
        $BOLD = "\e[1m";
    }

    # Set the process priority
    setpriority PRIO_PROCESS, 0, get_option('global', 'niceness');

    update_module_environment ('global');
}

# Subroutine to get a list of modules to install, either from the command line
# if it's not empty, or based on the list of modules successfully built.
sub get_install_list
{
    my @install_list;

    if ($#ARGV > -1)
    {
        @install_list = @ARGV;
        @ARGV = ();
    }
    else
    {
        # Get list of built items from $logdir/latest/build-status
        my $logdir = get_subdir_path('global', 'log-dir');

        if (not open BUILTLIST, "<$logdir/latest/build-status")
        {
            error "Can't determine what modules have built.  You must";
            error "specify explicitly on the command line what modules to build.";
            exit (1); # Don't finish, no lock has been taken.
        }

        while (<BUILTLIST>)
        {
            chomp;
            if (/Succeeded/)
            {
                # Clip to everything before the first colon.
                my $module = (split(/:/))[0];
                push @install_list, $module;
            }
        }

        close BUILTLIST;
    }

    return @install_list;
}

# Print out an error message, and a list of modules that match that error
# message.  It will also display the log file name if one can be determined.
# The message will be displayed all in uppercase, with PACKAGES prepended, so
# all you have to do is give a descriptive message of what this list of
# packages failed at doing.
sub output_failed_module_list($@)
{
    my ($message, @fail_list) = @_;
    $message = uc $message; # Be annoying

    debug "Message is $message";
    debug "\tfor ", join(', ', @fail_list);

    if (scalar @fail_list > 0)
    {
        my $homedir = $ENV{'HOME'};
        my $logfile;

        warning "\nr[b[<<<  PACKAGES $message  >>>]";

        for (@fail_list)
        {
            $logfile = get_option($_, '#error-log-file');

            # async updates may cause us not to have a error log file stored.  There's only
            # one place it should be though, take advantage of side-effect of log_command()
            # to find it.
            if (not $logfile) {
                my $logdir = get_log_dir($_) . "/error.log";
                $logfile = $logdir if -e $logdir;
            }

            $logfile = "No log file" unless $logfile;
            $logfile =~ s|$homedir|~|;

            warning "r[$_]" if pretending;
            warning "r[$_] - g[$logfile]" if not pretending;
        }
    }
}

# This subroutine reads the fail_lists dictionary to automatically call
# output_failed_module_list for all the module failures in one function
# call.
sub output_failed_module_lists()
{
    for my $type (@fail_display_order)
    {
        my @failures = @{$fail_lists{$type}};
        output_failed_module_list("failed to $type", @failures);
    }
}

# This subroutine extract the value from options of the form --option=value,
# which can also be expressed as --option value.  The first parameter is the
# option that the user passed to the cmd line (e.g. --prefix=/opt/foo), and
# the second parameter is a reference to the list of command line options.
# The return value is the value of the option (the list might be shorter by
# 1, copy it if you don't want it to change), or undef if no value was
# provided.
sub extract_option_value($\@)
{
    my ($option, $options_ref) = @_;

    if ($option =~ /=/)
    {
        my @value = split(/=/, $option);
        shift @value; # We don't need the first one, that the --option part.

        return undef if (scalar @value == 0);

        # If we have more than one element left in @value it's because the
        # option itself has an = in it, make sure it goes back in the answer.
        return join('=', @value);
    }

    return undef if scalar @{$options_ref} == 0;
    return shift @{$options_ref};
}

# Utility subroutine to handle setting the environment variable type of value.
# Returns true (non-zero) if this subroutine handled everything, 0 otherwise.
# The first parameter should by the reference to the hash with the 'set-env'
# hash ref, second parameter is the exact option to check, and the third
# option is the value to set that option to.
sub handle_set_env
{
    my ($href, $option, $value) = @_;

    return 0 if $option !~ /^#?set-env$/;

    my ($var, @values) = split(' ', $value);

    $$href{$option} = ( ) unless exists $$href{$option};
    $$href{$option}{$var} = join(' ', @values);

    return 1;
}

# Sets the option for the given module to the given value.  If the data for the
# module doesn't exist yet, it will be defined starting with a default value.
# First parameter: module to set option for (or 'global')
# Second parameter: option name (Preceded by # for a sticky option)
# Third parameter: option value
# Return value is void
sub set_option
{
    my ($module, $option, $value) = @_;

    # Set module options.  To see where the warning is not a bug, go to
    # read_options() where the module-specific options are set.
    if (not exists $package_opts{$module})
    {
        $package_opts{$module} = default_module_options($module);
    }

    return if handle_set_env($package_opts{$module}, $option, $value);

    debug "  Setting $module,$option = $value";
    $package_opts{$module}{$option} = $value;
}

# Subroutine to recursively deep copy (form a completely independent clone)
# of the modules given for module A (first parameter) to module B (second
# parameter).
#
# If options are already set for B, the function returns immediately to
# avoid overwriting already set options.
#
# No return value.
sub clone_options
{
    my ($a, $b) = @_;

    return if exists $package_opts{$b};

    $package_opts{$b} = { 'set-env' => { } };

    # set-env is special because it itself holds another reference.  All the
    # others can be copies.
    for my $key (keys %{$package_opts{$a}})
    {
        next if $key eq 'set-env';
        $package_opts{$b}{$key} = $package_opts{$a}{$key};
    }

    # Handle set-env options.
    for my $key (keys %{$package_opts{$a}{'set-env'}})
    {
        $package_opts{$b}{'set-env'}{$key} = $package_opts{$a}{'set-env'}{$key};
    }
}

# Subroutine to process the command line arguments.  Any arguments so
# processed will be removed from @ARGV.
# The arguments are generally documented in doc.html now.
# NOTE: Don't call finish() from this routine, the lock hasn't been obtained.
# NOTE: The options have not been loaded yet either.  Any option which
# requires more than rudimentary processing should set a flag for later work.
sub process_arguments
{
    my $arg;
    my $version = "kdesvn-build $versionNum";
    my $author = <<DONE;
$version was written (mostly) by:
  Michael Pyne <michael.pyne\@kdemail.net>

Many people have contributed code, bugfixes, and documentation.

Please report bugs using the KDE Bugzilla, at http://bugs.kde.org/
DONE

    my @argv;
    my @saved_opts = @ARGV; # Save options for debugging purposes.

    while ($_ = shift @ARGV)
    {
        SWITCH: {
            /^(--version)$/      && do { print "$version\n"; exit; };
            /^--author$/         && do { print $author; exit; };
            /^(-h)|(--?help)$/   && do {
                print <<DONE;
$version

This script automates the download, build, and install process for KDE (using
Subversion).

It is recommended that you first setup a .kdesvn-buildrc file in your home
directory.  Please visit http://kdesvn-build.kde.org/ for
information on how to write the file, or consult the sample file which should
have been included with this program.  If you don't setup a .kdesvn-buildrc,
a default set of options will be used, which a few modules to be built by
default.

After setting up .kdesvn-buildrc, you can run this program from either the
command-line or from cron.  It will automatically download the modules from
Subversion, create the build system, and configure and make the modules you
tell it to.  If you\'d like, you can use this program to install KDE as well,
if you\'re building KDE for a single user.  Note that kdesvn-build will try
by default to install the modules.

Basic synopsis, after setting up .kdesvn-buildrc:
\$ kdesvn-build [module names] (Download, build, and install KDE)

If you don\'t specify any particular module names, then your settings
in .kdesvn-buildrc will be used.  If you DO specify a module name, then
your settings will still be read, but the script will try to build/install
the modules in the order given on the command line.

Copyright (c) 2003 - 2007 $author
The script is distributed under the terms of the GNU General Public License
v2, and includes ABSOLUTELY NO WARRANTY!!!

Options:
    --no-svn             Skip contacting the Subversion server.
    --no-build           Skip the build process.
    --no-install         Don't automatically install after build.

    --svn-only           Update from Subversion only (Identical to --no-build
                         at this point).
    --build-only         Build only, don't perform updates or install.

    --pretend (or -p)    Don't actually contact the Subversion server, run make,
                         or create/delete files and directories.  Instead,
                         output what the script would have done.
    --quiet (or -q)      Be less descriptive of the build process, without
                         printing each little substep kdesvn-build is
                         performing.
    --really-quiet       Only warnings and errors will be displayed.
    --verbose (or -v)    Be *very* descriptive of the build process.  Only
                         --debug outputs more.
    --debug              Activates debug mode.
    --color
    --no-color           Add (or remove) color from the output.

    --rc-file=<filename> Read configuration from filename instead of default.
    --nice=<value>       Allows you to run the script with a lower priority
                         The default value is 10 (lower priority by 10 steps).
    --prefix=/kde/path   This option is a shortcut to change the setting for
                         kdedir from the command line.  It implies
                         --reconfigure.

    --resume-from=<pkg>  Starts building from the given package, without
                         performing the Subversion update.
    --revision (or -r)=<rev> Forces update to revision <rev> from Subversion.

    --refresh-build      Start the build from scratch.
    --reconfigure        Run configure again, but don't clean the build
                         directory or re-run make -f Makefile.cvs.
    --recreate-configure Run make -f Makefile.cvs again to redo the configure
                         script.
    --no-rebuild-on-fail Don't try to rebuild a module from scratch if it
                         failed building and we didn't already try to build it
                         from scratch.
    --build-system-only  Create the build infrastructure, but don't actually
                         perform the build.
    --run=<program>      Runs the given program in the same environment
                         kdesvn-build runs in.
    --install            Try to install the packages passed on the command
                         line, or all packages in ~/.kdesvn-buildrc that don't
                         have manual-build set.  Building and Subversion
                         updates are not performed.

    --<option>=          Any unrecognized options are added to the global
                         configuration, overriding any value that may exist.
    --<module>,<option>= Likewise, this allows you to override any module
                         specific option from the command line.

    --help               You\'re reading it. :-)
    --author             Output the author(s)\'s name.
    --version            Output the program version.

You can get more help by going online to http://kdesvn-build.kde.org/ to view
the online documentation.  The documentation is installed with the kdesdk
module, so you may be able to view the documentation using KHelpCenter or
Konqueror at help:/kdesvn-build
DONE
                # We haven't done any locking... no need to finish()
                #  Avoids log-dir errors due to having not performed.
                #  read_options() and setup_logging_subsystem().
                exit 0;
            };

            /^--install$/ && do {
                $install_flag = 1;
                last SWITCH;
            };

            # Option exists for debugging.  This disables downloading of
            # snapshots from kdesvn-build.kde.org (kdesvn-build won't even
            # check if it's possible).
            /^--no-snapshots$/ && do {
                set_option('global', '#disable-snapshot', 1);
                last SWITCH;
            };

            /^--no-svn$/ && do {
                set_option('global', '#no-svn', 1);
                last SWITCH;
            };

            /^--no-install$/ && do {
                set_option('global', '#install-after-build', 0);
                last SWITCH;
            };

            /^(-v)|(--verbose)$/ && do {
                set_option('global', '#debug-level', WHISPER);
                last SWITCH;
            };

            /^(-q)|(--quiet)$/ && do {
                set_option('global', '#debug-level', NOTE);
                last SWITCH;
            };

            /^--really-quiet$/ && do {
                set_option('global', '#debug-level', WARNING);
                last SWITCH;
            };

            /^--debug$/ && do {
                set_option('global', '#debug-level', DEBUG);
                debug "Commandline was: ", join(', ', @saved_opts);
                last SWITCH;
            };

            /^--reconfigure$/ && do {
                set_option('global', '#reconfigure', 1);
                last SWITCH;
            };

            /^--recreate-configure$/ && do {
                set_option('global', '#recreate-configure', 1);
                last SWITCH;
            };

            /^--color$/ && do {
                set_option('global', '#colorful-output', 1);
                last SWITCH;
            };

            /^--no-color$/ && do {
                set_option('global', '#colorful-output', 0);
                last SWITCH;
            };

            /^--no-build$/ && do {
                set_option('global', '#manual-build', 1);
                last SWITCH;
            };

            /^--async$/ && do {
                set_option('global', '#async', 1);
                last SWITCH;
            };

            /^--no-async$/ && do {
                set_option('global', '#async', 0);
                last SWITCH;
            };

            # Although equivalent to --no-build at this point, someday the
            # script may interpret the two differently, so get ready now.
            /^--svn-only$/ && do {      # Identically to --no-build
                set_option('global', '#manual-build', 1);
                last SWITCH;
            };

            # Don't run Subversion or install
            /^--build-only$/ && do {
                set_option('global', '#no-svn', 1);
                set_option('global', '#install-after-build', 0);
                last SWITCH;
            };

            # Start up a program with the environment variables as
            # read from the config file.
            /^--run=?/ && do {
                my $program = extract_option_value($_, @ARGV);
                if (not $program)
                {
                    print "You must specify a program to run with the --run option.\n";
                    exit 32;
                }

                set_option('global', '#start-program', $program);

                # Save remaining command line options to pass to the program.
                return;
            };

            /^--build-system-only$/ && do {
                set_option('global', '#build-system-only', 1);
                last SWITCH;
            };

            /^--rc-file=?/ && do {
                my $rcfile = extract_option_value($_, @ARGV);
                if (not $rcfile)
                {
                    print "You must specify a filename to use as the config file!\n";
                    exit 8;
                }

                @rcfiles = ( $rcfile );

                last SWITCH;
            };

            /^--prefix=?/ && do {
                my $prefix = extract_option_value($_, @ARGV);
                if (not $prefix)
                {
                    print "No prefix selected with the --prefix option.\n";
                    exit 8;
                }

                set_option('global', '#kdedir', $prefix);
                set_option('global', '#reconfigure', 1);

                last SWITCH;
            };

            /^--no-rebuild-on-fail$/ && do {
                set_option('global', '#no-rebuild-on-fail', 1);
                last SWITCH;
            };

            /^--nice=?/ && do {
                my $niceness = extract_option_value($_, @ARGV);

                if(defined $niceness)
                {
                    set_option('global', '#niceness', $niceness);
                }
                else
                {
                    print "You need to specify a value for the --nice option\n";
                    exit 8;
                }

                last SWITCH;
            };

            /^--ignore-modules$/ && do {
                # We need to keep read_options() from adding these modules to
                # the build list, taken care of by ignore_list.  We then need
                # to remove the modules from the command line, taken care of
                # by the @ARGV = () statement;
                my @options = ();
                foreach (@ARGV)
                {
                    if (/^-/)
                    {
                        push @options, $_;
                    }
                    else
                    {
                        $ignore_list{$_} = 1;

                        # the pattern match doesn't work with $_, alias it.
                        my $module = $_;
                        @argv = grep (!/^$module$/, @argv);
                    }
                }
                @ARGV = @options;

                last SWITCH;
            };

            /^(--dry-run)|(--pretend)|(-p)$/ && do {
                set_option('global', '#pretend', 1);
                last SWITCH;
            };

            /^--refresh-build$/ && do {
                set_option('global', '#refresh-build', 1);
                last SWITCH;
            };

            /^(--revision|-r)=?/ && do {
                my $revision = extract_option_value($_, @ARGV);
                if (not $revision)
                {
                    print "No revision selected with the --revision option.\n";
                    exit 8;
                }

                set_option('global', '#revision', $revision);

                last SWITCH;
            };

            /^--resume-from=?/ && do {
                $_ = extract_option_value($_, @ARGV);
                if (not $_)
                {
                    print "You must pass a module to resume from to the --resume-from option!\n";
                    exit 7;
                }

                set_option('global', '#resume-from', $_);
                set_option('global', '#no-svn', 1);
                last SWITCH;
            };

            /^--/ && do {
                # First let's see if they're trying to override a global option.
                my ($option) = /^--([-\w\d\/]+)/;
                my $value = extract_option_value($_, @ARGV);

                if (exists $package_opts{'global'}{$option})
                {
                    # Global option
                    set_option('global', "#$option", $value);
                }
                else
                {
                    # Module specific option.  The module options haven't been
                    # read in, so we'll just have to assume that the module the
                    # user passes actually does exist.
                    my ($module, $option) = /^--([\w\/-]+),([-\w\d\/]+)/;

                    if (not $module)
                    {
                        print "Unknown option $_\n";
                        exit 8;
                    }

                    # TODO: In pathological situations this may cause an
                    # inaccurate collection of options for this module due to
                    # the default options not being set correctly.  (i.e.
                    # use-stable-kde is false now, if set to true on command
                    # line the module options will be wrong).
                    set_option($module, "#$option", $value);
                }

                last SWITCH;
            };

            /^-/ && do { print "WARNING: Unknown option $_\n"; last SWITCH; };

            # Strip trailing slashes.
            s/\/*$//;
            push @argv, $_; # Reconstruct correct @ARGV
        }
    }

    @ARGV = @argv;

    # Don't go async if only performing one phase.  It (should) work but why
    # risk it?
    if(get_option('global', 'manual-build') or get_option('global', 'no-svn'))
    {
        set_option('global', '#async', 0);
    }
}

# Installs the given subroutine as a signal handler for a set of signals which
# could kill the program.
#
# First parameter is a reference to the sub to act as the handler.
sub install_signal_handler
{
    my $handler = shift;
    my @signals = qw/HUP INT QUIT ABRT TERM PIPE/;

    @SIG{@signals} = ($handler) x scalar @signals;
}

# Subroutine to try to get a lock on the script's lockfile to prevent
# more than one script from updating KDE Subversion at once.
# The value returned depends on the system's open() call.  Normally 0
# is failure and non-zero is success (e.g. a file descriptor to read).
# TODO: This could be improved to not fight over the lock when the scripts are
# handling separate tasks.
sub get_lock
{
    my $lockfile = "$ENV{HOME}/.kdesvn-lock";
    sysopen LOCKFILE, $lockfile, O_WRONLY | O_CREAT | O_EXCL;
    my $errorCode = $!; # Save for later testing.

    # Install signal handlers to ensure that the lockfile gets closed.
    # There is a race condition here, but at worst we have a stale lock
    # file, so I'm not *too* concerned.
    install_signal_handler(\&quit_handler);

    # Note that we can use color codes at this point since get_lock is called
    # after read_options (which sets up the color).
    if($errorCode == EEXIST)
    {
        # Path already exists, read the PID and see if it belongs to a
        # running process.
        open PIDFILE, "<$lockfile" or do
        {
            # Lockfile is there but we can't open it?!?  Maybe a race
            # condition but I have to give up somewhere.
            warning " WARNING: Can't open or create lockfile r[$lockfile]";
            return 1;
        };

        my $pid = <PIDFILE>;
        close PIDFILE;

        if($pid)
        {
            # Recent kdesvn-build; we wrote a PID in there.
            chomp $pid;

            # See if something's running with this PID.
            if (kill(0, $pid) == 1)
            {
                # Something *is* running, likely kdesvn-build.  Don't use error,
                # it'll scan for $!
                print clr " r[*y[*r[*] kdesvn-build appears to be running.  Do you want to:\n";
                print clr "  (b[Q])uit, (b[P])roceed anyways?: ";

                my $choice = <STDIN>;
                chomp $choice;

                if(lc $choice ne 'p')
                {
                    print clr " y[*] kdesvn-build run canceled.\n";
                    exit 1;
                }

                # We still can't grab the lockfile, let's just hope things
                # work out.
                print clr " y[*] kdesvn-build run in progress by user request.\n";
                return 1;
            }

            # If we get here, then the program isn't running (or at least not
            # as the current user), so allow the flow of execution to fall
            # through below and unlink the lockfile.
        } # pid

        # No pid found, optimistically assume the user isn't running
        # twice.
        warning " y[WARNING]: stale kdesvn-build lockfile found, deleting.";
        unlink $lockfile;
        sysopen LOCKFILE, $lockfile, O_WRONLY | O_CREAT | O_EXCL and do
        {
            print LOCKFILE "$$\n";
            close LOCKFILE;
        };
        return 1; # Hope the sysopen worked.
    }

    print LOCKFILE "$$\n";
    close LOCKFILE;

    # Even if we fail it's generally better to allow the script to proceed
    # without being a jerk about things, especially as more non-CLI-skilled
    # users start using kdesvn-build to build KDE.
    return 1;
}

# Subroutine to free the lock allocated by get_lock()
sub close_lock
{
    my $lockfile = "$ENV{HOME}/.kdesvn-lock";

    close LOCKFILE;
    unlink $lockfile;
}

sub adjust_update_list
{
    my $list_ref = shift;
    my $build_ref = shift;

    # Check to see if the user has requested for one of the modules to be
    # built using unsermake.  If so, we need to check if unsermake is
    # already supposed to be checked out.  If so, we need to make sure that
    # unsermake is present in any checkout-only directives, and if not, we need
    # to add unsermake to the checkout list.
    my $unsermake_needed = 0;

    for my $module (@{$build_ref})
    {
        if(get_option($module, 'use-unsermake') and not module_uses_cmake($module))
        {
            debug "Unsermake is needed.";
            $unsermake_needed = 1;
            last;
        }
    }

    # If the user has told us that they will manage unsermake then we don't
    # need to do anything.
    $unsermake_needed = 0 if get_option('global', 'use-unsermake') eq 'self';

    # If the user has set manual-update, don't second-guess them.
    $unsermake_needed = 0 if get_option('unsermake', 'manual-update');

    debug "Do we update unsermake? ", ($unsermake_needed ? 'yes' : 'no');

    if ($unsermake_needed)
    {
        if (not list_has(@{$list_ref}, 'unsermake'))
        {
            whisper "Adding unsermake to build.";

            # unsermake isn't being downloaded by the user.
            unshift (@{$list_ref}, 'unsermake');
            $package_opts{'unsermake'} = {
                'manual-build' => 'true',
                'tag'          => 'unmaintained/3',
            };
        }
    }
}

# Subroutine to get the list of Subversion modules to update.  Returned
# as a list.  Parse the command-line arguments first.
sub get_update_list
{
    return @ARGV unless $#ARGV == -1;

    # l10n module has different names for KDE 3 and KDE 4.
    my $l10n = 'l10n-kde4';
    $l10n = 'l10n-kde3' if get_option('global', 'use-stable-kde');

    if (get_option('global', 'kde-languages') and not get_option($l10n, 'manual-update'))
    {
        push @update_list, $l10n; # Add l10n module to potential update list.
    }

    my @return_list;
    for (@update_list)
    {
        push @return_list, $_ if not get_option($_, "manual-update");
    }

    return @return_list;
}

# Subroutine to get the list of Subversion modules to build.  Returned
# as a list.  A module will not be built if manual-build is set
# in the module's options.  The command-line arguments should have been
# parsed first.
#
# This subroutine will handle the --resume-from options.
sub get_build_list
{
    my $resume_point = get_option('global', '#resume-from');

    # l10n module has different names for KDE 3 and KDE 4.
    my $l10n = 'l10n-kde4';
    $l10n = 'l10n-kde3' if get_option('global', 'use-stable-kde');

    # Add l10n to build list if needed.
    if (get_option('global', 'kde-languages') and not get_option($l10n, 'manual-build'))
    {
        push @build_list, $l10n;
    }

    # We check explicitly for sticky options here since they can only be
    # set from the command line.
    if (get_option('global', '#manual-build'))
    {
        if ($resume_point)
        {
            warning "I'm confused, you enabled y[--no-build] and y[--resume-from].";
            warning "Skipping the build process.";
        }

        return ();
    }

    if ($resume_point)
    {
        # Pop stuff off of the list until we hit the resume point.
        while (scalar @build_list > 0)
        {
            last if $build_list[0] eq $resume_point;
            shift @build_list;
        }

        if (not @build_list)
        {
            warning "Can't resume from y[$resume_point], it wasn't going to be built!";
        }

        return @build_list;
    }

    return @ARGV unless $#ARGV == -1;

    my @list;
    for (@build_list)
    {
        push @list, $_ unless get_option($_, 'manual-update');
    }

    return @list;
}

# Used to sort module names.  'global' always starts first, modules with /
# sort last.
sub module_sort
{
    # This is always true.
    return 0 if $a eq $b;

    # Look for global modules.
    return -1 if $a eq 'global';
    return 1 if $b eq 'global';

    # If both have /, use a normal sort.
    return $a cmp $b if $a =~ /\// and $b =~ /\//;

    # If left has slash, it's < $b (and vice versa)
    return 1 if $a =~ /\//;
    return -1 if $b =~ /\//;

    # Normal sort.
    return $a cmp $b;
}

# Helper subroutine for debugging purposes.  Dumps all of the
# options which have been read in to %package_opts.
sub dump_options
{
    my ($item, $ref_item, $ref);
    my @keys = sort module_sort keys %package_opts;
    my $c; # $c is a color variable to be used with clr()

    # Now dump the options for each module
    foreach $item (@keys)
    {
        debug "\nOptions for module g[$item]:";
        my $ref = $package_opts{$item};

        foreach $ref_item (sort keys %{$package_opts{$item}})
        {
            # Put the first bracket in here, otherwise it breaks on some
            # Perl systems.
            $c = $ref_item =~ /^#/ ? 'r[' : 'g[';

            if($ref_item !~ /^#?set-env$/)
            {
                next unless defined $$ref{$ref_item};
                debug "  ${c}$ref_item] is \"y[", $$ref{$ref_item}, clr ']"';
            }
            else
            {
                # Dump the environment variables that will be set.
                my $setref = $$ref{$ref_item};

                foreach my $envitem (keys %{$setref})
                {
                    debug "  Set env variable ${c}$envitem] to y[", $$setref{$envitem};
                }
            }
        }
    }
}

# Subroutine to rename the given file if global-pretend isn't set.
sub safe_rename($$)
{
    my ($from, $to) = @_;

    if (pretending)
    {
        pretend "\tWould have renamed '$from' to '$to'.";
        return 1; # Return true
    }

    return rename($from, $to);
}

# Subroutine to unlink the given symlink if global-pretend isn't set.
sub safe_unlink
{
    if (pretending)
    {
        pretend "\tWould have unlinked ", shift, ".";
        return 1; # Return true
    }

    return unlink (shift);
}

# Subroutine to execute the system call on the given list if the pretend
# global option is not set.
#
# Returns the shell error code, so 0 means success, non-zero means failure.
sub safe_system(@)
{
    if (not pretending)
    {
        info "\tExecuting g[", join(" ", @_);
        return system (@_) >> 8;
    }

    pretend "\tWould have run g[", join(' ', @_);
    return 0; # Return true
}

# Is exactly like "chdir", but it will also print out a message saying that
# we're switching to the directory when debugging.
sub p_chdir($)
{
    my $dir = shift;
    debug "\tcd g[$dir]\n";
    chdir $dir;
}

# Helper subroutine to create a directory, including any parent
# directories that may also need created.
# Returns 0 on failure, non-zero on success
sub super_mkdir
{
    my $pathname = shift;
    my $temp;
    my @parts = split (/\//, $pathname);

    if (pretending)
    {
        pretend "\tWould have created g[$pathname]";
        return 1;
    }

    foreach (@parts)
    {
        $temp .= "$_/";

        next if -e $temp;

        # Check again if the directory does not exist before failing, since this
        # function may be run concurrently in different processes.
        return 0 if not mkdir ($temp) and not -e $temp;
    }

    return 1;
}

# Subroutine to remove a package from the package build list.  This
# is for use when you've detected an error that should keep the
# package from building, but you don't want to abort completely.
sub dont_build
{
    my $module = shift;
    my $ipc = shift;

    whisper "Not building $module";

    if ($ipc)
    {
        $ipc->sendIPCMessage(IPC_MODULE_FAILURE, $module);
    }
    else
    {
        # Weed out matches of the module name
        @build_list = grep (!/^$module$/, @build_list);
    }

    push @{$fail_lists{'update'}}, $module;
}

# Subroutine to split a url into a protocol and host
sub split_url
{
    my $url = shift;
    my ($proto, $host) = ($url =~ m|([^:]*)://([^/]*)/|);

    return ($proto, $host);
}

# This subroutine checks if we are supposed to use ssh agent by examining the
# environment, and if so checks if ssh-agent has a list of identities.  If it
# doesn't, we run ssh-add (with no arguments) and inform the user.  This can
# be controlled with the disable-agent-check parameter.
sub check_for_ssh_agent
{
    my $agent_running = 0;
    my $server = get_option('global', 'svn-server');
    my ($proto, $host) = split_url($server);

    # Don't bother with all this if the user isn't even using SSH.
    return 1 if($proto !~ /ssh/) or get_option('global', 'disable-agent-check');
    return 1 if pretending;

    # We're using ssh to download, see if ssh-agent is running.
    return 1 unless exists $ENV{'SSH_AGENT_PID'};

    my $pid = $ENV{'SSH_AGENT_PID'};

    # It's supposed to be running, let's see if there exists the program with
    # that pid (this check is linux-specific at the moment).
    if (-d "/proc" and not -e "/proc/$pid")
    {
        warning "r[ *] SSH Agent is enabled, but y[doesn't seem to be running].";
        warning "Since SSH is used to download from Subversion you may want to see why";
        warning "SSH Agent is not working, or correct the environment variable settings.";

        return 0;
    }

    # The agent is running, but does it have any keys?  We can't be more specific
    # with this check because we don't know what key is required.
    my $keys = `ssh-add -l 2>/dev/null`;
    if ($keys =~ /no identities/)
    {
        # Use print so user can't inadvertently keep us quiet about this.
        print clr <<EOF;
b[y[*] SSH Agent does not appear to be managing any keys.  This will lead to you
  being prompted for every module update for your SSH passphrase.  So, we're
  running g[ssh-add] for you.  Please type your passphrase at the prompt when
  requested, (or simply Ctrl-C to abort the script).
EOF
        my $result = system('ssh-add');
        if ($result) # Run this code for both death-by-signal and nonzero return
        {
            print "\nUnable to add SSH identity, aborting.\n";
            print "If you don't want kdesvn-build to check in the future,\n";
            print clr "Set the g[disable-agent-check] option to g[true] in your $rcfile.\n\n";

            return 0;
        }
    }

    return 1;
}

# Subroutine to update a list of Subversion modules.  The first
# parameter is a reference of a list of the modules to update.
# If the module has not already been checkout out, this subroutine
# will do so for you.
#
# The $ipc variable contains an object that is responsible for communicating
# the status of building the modules.  This function must account for every
# module in @update_list to $ipc before returning.
#
# Returns 0 on success, non-zero on error.
sub handle_updates
{
    my $ipc = shift;
    my $kdesvn = get_kdesvn_dir();
    my $hadError = 0;

    # No reason to print out the text if we're not doing anything.
    if (scalar @update_list == 0)
    {
        $ipc->sendIPCMessage(IPC_ALL_UPDATING, "update-list-empty");
        return 0;
    }

    if (get_option ('global', 'no-svn'))
    {
        $ipc->sendIPCMessage(IPC_ALL_SKIPPED, "no-svn");
        return 0;
    }

    if (not check_for_ssh_agent())
    {
        $ipc->sendIPCMessage(IPC_ALL_FAILURE, "ssh-failure");
        return 1;
    }

    # Be much quieter if operating multiprocess.
    set_option('global', '#debug-level', WARNING) if $ipc->supportsConcurrency();

    note "<<<  Updating Subversion Directories  >>>";
    info " "; # Add newline for aesthetics unless in quiet mode.

    if (not -e $kdesvn)
    {
        whisper "KDE Subversion download directory doesn't exist, creating.\n";
        if (not super_mkdir ($kdesvn))
        {
            error "Unable to make directory r[$kdesvn]!";
            $ipc->sendIPCMessage(IPC_ALL_FAILURE, "no-source-dir");

            return 1;
        }
    }

    # Once at this point, any errors we get should be limited to a module,
    # which means we can tell the build thread to start.
    $ipc->sendIPCMessage(IPC_ALL_UPDATING, "starting-updates");

    # Make sure KDE's SSL signature is present since --non-interactive is
    # passed to svn.
    install_missing_ssl_signature();

    # Calculate l10n module name.
    my $l10n = 'l10n-kde4';
    $l10n = 'l10n-kde3' if get_option('global', 'use-stable-kde');

    foreach my $module (@update_list)
    {
        check_for_module_config ($module);

        if(get_option($module, 'no-svn'))
        {
            $ipc->sendIPCMessage(IPC_MODULE_SKIPPED, $module);
            next;
        }

        my @options = split(' ', get_option($module, 'checkout-only'));
        $module = $l10n if $module eq 'l10n'; # Correct internal name.

        # Use kde-langauges option if this isn't set on command line.
        if ($module eq $l10n and not scalar @options)
        {
            push @options, split(' ', get_option('global', 'kde-languages'));
        }

        # Make sure each submodule of l10n has their own options hash.
        if ($module eq $l10n)
        {
            # Ensure that scripts is pulled in for the update process (not req'd
            # for build or install)
            push @options, 'scripts';

            for my $lang (@options)
            {
                clone_options($l10n, "$l10n/$lang");
            }
        }

        my $fullpath = get_fullpath($module, 'source');
        my $count;

        eval {
            if (-e "$fullpath/.svn")
            {
                # Warn user if the current repo URL is different than expected.
                check_module_validity($module);
                $count = update_module_path($module, @options);
            }
            else
            {
                $count = checkout_module_path($module, @options);
            }
        };

        if ($@)
        {
            error "Error updating r[$module], removing from list of packages to build.";
            dont_build ($module, $ipc); # Sends IPC message.
            $hadError = 1;
        }
        else
        {
            my $message;
            if (not defined $count)
            {
                $message = clr "b[y[Unknown changes]." if not defined $count;
            }
            else
            {
                $message = "1 file affected." if $count == 1;
                $message = "$count files affected." if $count != 1;
            }

            $ipc->notifyUpdateSuccess($module, $message);
        }

        info ""; # Print empty line.
    }

    info "<<<  Update Complete  >>>\n";
    return $hadError;
}

# Subroutine to run the qt-copy apply_patches script.
# Returns 0 on success, non-zero on failure.
sub safe_apply_patches
{
    my $srcdir = get_fullpath('qt-copy', 'source');

    if (pretending)
    {
        pretend "\tWould have run g[./apply_patches]";
        return 0;
    }

    info "\tg[Applying recommended Qt patches].";
    p_chdir ($srcdir);
    return (log_command('qt-copy', 'apply-patches', [ "./apply_patches" ]));
}

# Subroutine to run and log the configure command.  First parameter is the
# path to the configure script to run, the second parameter is a scalar
# containing all of the configure flags to apply
sub safe_configure
{
    my $module = shift;
    my $srcdir = get_fullpath($module, 'source');
    my $script = "$srcdir/configure";

    my @commands = split (/\s+/, get_option($module, 'configure-flags'));

    # Get the user's CXXFLAGS
    my $cxxflags = get_option ($module, 'cxxflags');
    setenv ('CXXFLAGS', $cxxflags);
    setenv ('DO_NOT_COMPILE', get_option ($module, 'do-not-compile'));

    if ($module ne 'qt-copy')
    {
        my $prefix = get_option ($module, 'prefix');

        # If still no prefix, use KDEDIR
        $prefix = get_option($module, 'kdedir') unless $prefix;

        push @commands, "CXXFLAGS=$cxxflags" if $cxxflags;
        push @commands, "--prefix=$prefix";
    }
    else # qt-copy
    {
        my $prefix = get_option($module, 'qtdir');

        # Some users have added -prefix manually to their flags, they
        # probably shouldn't anymore. :)

        if (scalar grep /^-prefix(=.*)?$/, @commands)
        {
            warning <<EOF;
 b[y[*]
 b[y[*] You have the y[-prefix] option selected in your qt-copy configure flags.
 b[y[*] kdesvn-build will correctly add the -prefix option to match your Qt
 b[y[*] directory setting, so you do not need to use -prefix yourself.
 b[y[*]
EOF
        }

        push @commands, "-prefix", $prefix;
    }

    # We're special casing these modules because we're using the lndir
    # hack for them.
    if (module_needs_builddir_help($module))
    {
        $script = get_fullpath($module, 'build') . "/configure";
    }

    # Create specialized configure script for qt-copy.
    if ($module eq 'qt-copy')
    {
        my $qtdir = get_fullpath('qt-copy', 'source');

        debug "Creating g[$qtdir/configure.new] from g[$script]";

        if(not pretending)
        {
            # Copy the configure script to accept the GPL license.

            # $script should point to $srcdir/qt-copy/configure for both Qt
            # 3 and 4.

            open CONFIG, "<$script";
            open NEWCONFIG, ">$qtdir/configure.new";

            while(<CONFIG>)
            {
                s/read acceptance/acceptance=yes/;
                print NEWCONFIG $_;
            }

            close NEWCONFIG;
            close CONFIG;

            chmod 0755, "$qtdir/configure.new";
        }

        $script = "$qtdir/configure.new";

        note "\tb[r[GPL license selected for Qt].  See $srcdir/LICENSE.GPL";
    }

    info "\tRunning g[configure]...";
    unshift @commands, $script;

    return log_command($module, "configure", \@commands);
}

# Subroutine to run CMake to create the build directory for a module.
# CMake is not actually run if pretend mode is enabled.
#
# First parameter is the module to run cmake on.
# Return value is the shell return value as returned by log_command().  i.e.
# 0 for success, non-zero for failure.
sub safe_run_cmake
{
    my $module = shift;
    my $srcdir = get_fullpath($module, 'source');
    my @commands = split (/\s+/, get_option($module, 'cmake-options'));

    # grep out empty fields
    @commands = grep {!/^$/} @commands;

    if (get_option ($module, 'do-not-compile'))
    {
        warning " y[*] kdesvn-build does not support y[do-not-compile] with CMake.";
    }

    # Get the user's CXXFLAGS, use them if specified and not already given
    # on the command line.
    my $cxxflags = get_option ($module, 'cxxflags');
    if ($cxxflags and not grep { /^-DCMAKE_CXX_FLAGS=/ } @commands)
    {
        push @commands, "-DCMAKE_CXX_FLAGS=$cxxflags";
    }

    my $prefix = get_option ($module, 'prefix');

    # If still no prefix, use KDEDIR
    $prefix = get_option($module, 'kdedir') unless $prefix;

    push @commands, "-DCMAKE_INSTALL_PREFIX=$prefix";

    # For l10n-kde4, we must run cmake to search in the (fake) build dir.  There
    # is no srcdir != builddir (but we do simulate it).
    if ($module =~ /^l10n-kde4\/?/)
    {
        $srcdir = ".";
    }

    info "\tRunning g[cmake]...";
    unshift @commands, 'cmake', $srcdir; # Add to beginning of list.

    # Remove any stray CMakeCache.txt
    my $builddir = get_fullpath($module, 'build');
    safe_unlink "$srcdir/CMakeCache.txt"   if -e "$srcdir/CMakeCache.txt";
    safe_unlink "$builddir/CMakeCache.txt" if -e "$builddir/CMakeCache.txt";

    return log_command($module, "cmake", \@commands);
}

# Subroutine to try and see if we've already tried to update kde-common
sub has_updated_kdecommon
{
    # Test fast case first.
    return 1 if get_option('global', '#has-checked-for-admin');

    # Double check that it wasn't in the update list.
    if (grep(/^(KDE\/)?kde-common$/, @update_list))
    {
        set_option('global', '#has-checked-for-admin', 1);
        return 1;
    }

    return 0;
}

# Subroutine to automatically create an admir dir for a module if it doesn't
# have one.  The first parameter is the module name.  It is assumed that we
# are already in the source directory, the current directory will not be
# changed.
#
# Returns boolean true on success, boolean false on failure.
#
# NOTE: This subroutine might try to call an svn update, as long as #no-svn
# isn't set.
sub create_admin_dir
{
    my $module = shift;
    my $fullpath = get_fullpath($module, 'source');

    # Don't bother if it's qt-copy, or if we've already got an admin
    # directory.
    return 1 if $module eq 'qt-copy';
    return 1 if -e "$fullpath/admin";

    # Find kde-common
    my $admindir = get_fullpath('kde-common', 'source') . '/admin';
    if (not -e $admindir)
    {
        $admindir = get_fullpath('KDE/kde-common', 'source') . '/admin';
    }

    if (not -e $admindir)
    {
        # Can't find kde-common, it's apparently not installed.
        if (not has_updated_kdecommon())
        {
            # We haven't tried downloading it, now would be a good time.
            note "Can't find y[kde-common], going to try downloading it.";

            if (get_option('kde-common', 'no-svn'))
            {
                # Not allowed to update.
                error "r[!!] Updating has been blocked, can't get y[kde-common].";
                return 0;
            }

            # Checkout the directory.
            $admindir = get_fullpath('kde-common', 'source') . '/admin';
            if (pretending)
            {
                pretend "Would have checked out g[kde-common]\n";
            }
            else
            {
                eval {
                    checkout_module_path('kde-common', 'admin');
                };

                return 0 if $@;
            }
        }
    }

    p_chdir ($fullpath);

    whisper "\tCreating symbolic link to g[/admin directory].";
    return 1 if pretending;

    return symlink $admindir, "$fullpath/admin";
}

# Subroutine to recursively symlink a directory into another location, in a
# similar fashion to how the XFree/X.org lndir() program does it.  This is
# reimplemented here since some systems lndir doesn't seem to work right.
#
# As a special exception to the GNU GPL, you may use and redistribute this
# function however you would like (i.e. consider it public domain).
#
# The first parameter is the directory to symlink from.
# The second parameter is the destination directory name.
#
# e.g. if you have $from/foo and $from/bar, lndir would create $to/foo and
# $to/bar.
#
# All intervening directories will be created as needed.  In addition, you
# may safely run this function again if you only want to catch additional files
# in the source directory.
#
# Note that this function will unconditionally output the files/directories
# created, as it is meant to be a close match to lndir.
#
# RETURN VALUE: Boolean true (non-zero) if successful, Boolean false (0, "")
#               if unsuccessful.
sub safe_lndir
{
    my ($from, $to) = @_;

    # Create destination directory.
    if (not -e $to)
    {
        print "$to\n";
        if (not pretending and not super_mkdir($to))
        {
            error "Couldn't create directory r[$to]: b[r[$!]";
            return 0;
        }
    }

    # Create closure callback subroutine.
    my $wanted = sub {
        my $dir = $File::Find::dir;
        my $file = $File::Find::fullname;
        $dir =~ s/$from/$to/;

        # Ignore the .svn directory and files.
        return if $dir =~ m,/\.svn,;

        # Create the directory.
        if (not -e $dir)
        {
            print "$dir\n";

            if (not pretending)
            {
                super_mkdir ($dir) or die "Couldn't create directory $dir: $!";
            }
        }

        # Symlink the file.  Check if it's a regular file because File::Find
        # has no qualms about telling you you have a file called "foo/bar"
        # before pointing out that it was really a directory.
        if (-f $file and not -e "$dir/$_")
        {
            print "$dir/$_\n";

            if (not pretending)
            {
                symlink $File::Find::fullname, "$dir/$_" or
                    die "Couldn't create file $dir/$_: $!";
            }
        }
    };

    # Recursively descend from source dir using File::Find
    eval {
        find ({ 'wanted' => $wanted,
                'follow_fast' => 1,
                'follow_skip' => 2},
              $from);
    };

    if ($@)
    {
        error "Unable to symlink $from to $to: $@";
        return 0;
    }

    return 1;
}

# Subroutine to link a source directory into an alternate directory in order
# to fake srcdir != builddir for modules that don't natively support it.
# The first parameter is the module to prepare.
#
# The return value is true (non-zero) if it succeeded, and 0 (false) if it
# failed.
#
# On return from the subroutine the current directory will be in the build
# directory, since that's the only directory you should touch from then on.
#
# You may safely call this subroutine for modules that don't need it, they
# will automatically be ignored.
sub prepare_fake_builddir
{
    my $module = shift;
    my $builddir = get_fullpath($module, 'build');
    my $srcdir = get_fullpath($module, 'source');

    # List reference, not a real list.  The initial kdesvn-build does *NOT*
    # fork another kdesvn-build using exec, see sub log_command() for more
    # info.
    my $args = [ 'kdesvn-build', 'safe_lndir', $srcdir, $builddir ];

    # Skip modules that don't need special treatment.
    return 1 unless module_needs_builddir_help($module);

    # Backwards compatibility hack.
    # kdesvn-build 0.97 and earlier would physically copy the Qt source
    # directory to the build directory.  kdesvn-build versions after that use
    # the lndir program that is used for kdebindings and valgrind for
    # portability reasons.  This will break for users who have a real copy of
    # Qt, so check here if the qt-copy configure script file is a real file
    # (not a symlink), and if so, use the old method (since presumably it
    # worked earlier).
    if ($module eq 'qt-copy' and -e "$builddir/configure" and not -l "$builddir/configure")
    {
        whisper "Using deprecated qt-copy builddir faking method.";

        # Use old method of copying.
        $args = [ 'cp', '-af', $srcdir, $builddir ];
    }

    # Use an internal routine to complete the directory symlinking (or the
    # alternate routine in the case of old qt-copy).
    if (log_command ($module, 'create-builddir', $args))
    {
        warning "\tUnable to setup special build system for r[$module].";
        return 0;
    }

    return 1; # Success
}

# Subroutine to create the build system for a module.  This involves making
# sure the directory exists and then running make -f Makefile.cvs.  This
# subroutine assumes that the module is already downloaded.
sub safe_create_build_system
{
    my $module = shift;
    my $srcdir = get_fullpath($module, 'source');
    my $builddir = get_fullpath($module, 'build');
    my $uses_cmake = module_uses_cmake($module);
    my $instapps = get_option($module, 'inst-apps');

    whisper "\tCMake support for $module: ", $uses_cmake ? "Yes" : "No";

    if (pretending)
    {
        pretend "\tWould have created g[$module]\'s build system.";
        return 0;
    }

    # If we're using CMake, stick around long enough to check for inst-apps,
    # and eventually other valid options we don't support yet, and warn if
    # they are in use.  We do nothing further in this function because we
    # skip this step when using CMake.
    # Exception: KDE 4 l10n modules use CMake but require extra preparation
    # before they can be built, so don't abort out for that module.
    if ($uses_cmake)
    {
        warning " b[y[*] CMake does not support the inst-apps option!" if $instapps;
        return 0 unless $module =~ /^l10n-kde4\/?/; # Done.
    }

    # These modules will run make -f Makefile.cvs in (fake) builddir to keep
    # srcdir clean. Except for qt-copy when not using qt-builddir-hack.
    if(module_needs_builddir_help($module))
    {
        p_chdir ($builddir);
    }
    else
    {
        p_chdir ($srcdir); # Run make -f Makefile.cvs in srcdir.
    }

    if ($instapps)
    {
        open (INSTAPPS, ">inst-apps") or do {
            error "\tUnable to create inst-apps file for r[$module]!";
            return 1;
        };

        print INSTAPPS "$instapps\n";
        close INSTAPPS;
    }
    else
    {
        unlink ("$srcdir/inst-apps");
    }

    # qt-copy 4.x doesn't need any other help.
    if ($module eq 'qt-copy' and not -e 'Makefile.cvs')
    {
        return 0;
    }

    my $cmd_ref = [ 'make', '-f', 'Makefile.cvs' ];
    $cmd_ref = [ './autogen.sh' ] if $module eq 'valgrind';

    if ($module =~ /^l10n-kde[34]\//)
    {
        my ($lang) = ($module =~ /^l10n-kde[34]\/(.*)$/);

        # autogen.sh must be run from one level below for some reason.
        p_chdir ("../");

        $cmd_ref = [ './scripts/autogen.sh', $lang ];
    }

    if (log_command ($module, "build-system", $cmd_ref))
    {
        error "\tUnable to create build system for r[$module]";
        return 1;
    }

    return 0;
}

# Subroutine to determine if a given module needs to have the build system
# recreated from scratch.
# If so, it returns boolean true.
sub needs_refreshed
{
    my $module = shift;
    my $builddir = get_fullpath($module, 'build');
    my $conf_file_key = "Makefile"; # File that exists after configure is run

    if (debugging)
    {
        debug "Build directory not setup for $module." if not -e "$builddir";
        debug ".refresh-me exists for $module." if -e "$builddir/.refresh-me";
        debug "refresh-build option set for $module." if get_option($module, 'refresh-build');
        debug "Can't find configure key file for $module." if not -e "$builddir/$conf_file_key";
    }

    return 1 if ((not -e "$builddir") ||
        (-e "$builddir/.refresh-me") ||
        get_option($module, "refresh-build") ||
        (not -e "$builddir/$conf_file_key"));

    return 0;
}

# Run the svn command.  This is a special subroutine so that we can munge the
# generated output to see what files have been added, and adjust the build
# according.
#
# This function will throw an exception in the event of a build failure.
#
# First parameter is the module we're building.
# Second parameter is the filename to use for the log file.
# Third parameter is a reference to a list, which is the command ('svn') and all
#       of its arguments.
# Return value is the number of files update (may be undef if unable to tell)
sub run_svn
{
    my ($module, $logfilename, $arg_ref) = @_;
    my $force_refresh = 0;
    my $conflict = 0;

    my $revision = get_option($module, 'revision');
    if ($revision ne '0')
    {
        my @tmp = @{$arg_ref};

        # Insert after first two entries, deleting 0 entries from the
        # list.
        splice @tmp, 2, 0, '-r', $revision;
        $arg_ref = \@tmp;
    }

    # Do svn update.
    my $result = log_command($module, $logfilename, $arg_ref);

    return 0 if pretending;

    # Exception handling. Scary!
    die "Error updating $module: $!" unless $result == 0;

    my $logdir = get_log_dir($module);
    $logfilename = "$logdir/$logfilename.log";

    # We need to open the file and try to determine what the Subversion process
    # did.
    open SVN_LOG, "<$logfilename" or return undef;

    my $count = 0;
    while (<SVN_LOG>)
    {
        # The check for capitalized letters in the second column is because
        # svn can use the first six columns for updates (the characters will
        # all be uppercase), which makes it hard to tell apart from normal
        # sentences (like "At Revision foo"

        # Count all changes to the files.
        $count++ if /^[UPDARGMC][ A-Z]/;

        # Check if we need to force a refresh.
        $force_refresh = 1 if /^A[ A-Z]/ and /Makefile\.am/;
        $force_refresh = 1 if /^[PAMGU][ A-Z]/ and /configure\.in\.in/;

        $conflict = 1 if /^C[ A-Z]/;
    }

    close SVN_LOG;

    if ($conflict)
    {
        warning "Source code conflict exists in r[$module], this module will not";
        warning "build until it is resolved.";

        die "Source conflicts exist in $module";
    }

    if ($force_refresh and -e get_fullpath($module, 'build'))
    {
        info "File(s) related to the build system were updated, forcing a refresh.";
        set_option($module, 'refresh-build', 1);
        set_option($module, '#cancel-clean', 1);
    }

    return $count;
}

# Subroutine to delete recursively, everything under the given directory,
# unless we're in pretend mode.
#
# i.e. the effect is similar to "rm -r $arg/* $arg/.*".
#
# The first parameter should be the absolute path to the directory to delete.
#
# Returns boolean true on success, boolean false on failure.
sub prune_under_directory
{
    my $dir = shift;

    debug "Deleting under r[$dir]";

    # This closure subroutine will be called for every file/directory.
    # It will be called in a postorder traversal (i.e. parent processed after
    # all the children).
    my $wanted = sub {
        my $name = $File::Find::name;

        # Don't delete the parent directory, just everything under.
        return if ($name eq $dir);

        lstat ($name); # stats the actual symlink.  Called now for caching.

        if (not -l _ and -d _)
        {
            # Remove directory.
            debug "Removing directory y[$name]";
            if (not pretending)
            {
                rmdir ($name) or die "Couldn't delete $name!: $!";
            }
        }
        else
        {
            # Remove file/symlink/etc.
            debug "Removing file y[$name]";
            if (not pretending)
            {
                unlink ($name) or die "Couldn't delete $name!: $!";
            }
        }
    };

    # Call recursive find.
    eval {
        finddepth ($wanted, $dir);
    };

    if ($@)
    {
        error "\tUnable to clean r[$dir]:\n\ty[b[$@]";
        return 0;
    }

    return 1;
}

# Subroutine to clean the build system for the given module.  Works by
# recursively deleting the directory and then recreating it.  Returns
# 0 for failure, non-zero for success.
sub clean_build_system
{
    my $module = shift;
    my $moduledir = get_fullpath($module, 'source');
    my $builddir = get_fullpath($module, 'build');

    if (pretending)
    {
        pretend "\tWould have cleaned build system for g[$module]";
        return 1;
    }

    if (not -e $moduledir)
    {
        warning "\tUnable to clean build system for r[$module], it's not been checked out!";
        return 0;
    }

    # Use an existing directory
    if (-e "$builddir" && "$builddir" ne "$moduledir")
    {
        info "\tRemoving files in build directory for g[$module]";

        if (not prune_under_directory($builddir))
        {
            return 0; # False for this function.
        }

        # Let users know we're done so they don't wonder why rm -rf is taking so
        # long and oh yeah, why's my HD so active?...
        info "\tOld build system cleaned, starting new build system.";
    }
    # or create the directory
    elsif (not super_mkdir ("$builddir"))
    {
        error "\tUnable to create directory r[$builddir].";
        return 0;
    }

    return 1;
}

# Subroutine to setup the build system in a directory.  The first parameter
# is the module name.  Returns boolean true on success, boolean false (0)
# on failure.
sub setup_build_system
{
    my $module = shift;
    my $srcdir = get_fullpath($module, 'source');
    my $builddir = get_fullpath($module, 'build');
    my $uses_cmake = module_uses_cmake($module);
    my $do_configure = get_option ($module, 'reconfigure');

    # We don't need to do this with CMake.  Parentheses required because otherwise
    # Perl parses as ($a = get_option()) and not foo.  This could also be fixed
    # by using && instead of and.
    my $do_makeconf = (get_option ($module, 'recreate-configure') and not $uses_cmake);

    # As a special case to the normal instances where we will rebuild a module,
    # also force a rebuild if we're using CMake but the current build directory
    # is unsermake-based.  libtool is only used by autotools/unsermake.
    if (needs_refreshed($module) or
        ($uses_cmake and -e "$builddir/libtool"))
    {
        # The build system needs created, either because it doesn't exist, or
        # because the user has asked that it be completely rebuilt.
        info "\tPreparing build system for y[$module].";

        if ($uses_cmake and -e "$builddir/libtool")
        {
            info "\t\ty[Rebuild forced] due to switch of build system to CMake.";
        }

        # Define this option to tell later functions that we tried to rebuild
        # this module.
        set_option($module, '#was-rebuilt', 1);

        # Check to see if we're actually supposed to go through the cleaning
        # process.
        if (not get_option($module, '#cancel-clean') and
            not clean_build_system($module))
        {
            warning "\tUnable to clean r[$module]!";
            return 0;
        }

        $do_makeconf = 1;
    }

    # Symlink source directory to build directory if module doesn't support
    # srcdir != builddir.  If it's qt-copy only do so if it is Qt 3.
    # Note that module_needs_builddir_help() already takes care of that test.
    if (not $uses_cmake and module_needs_builddir_help($module))
    {
        whisper "\tFaking builddir for g[$module]";
        if (not prepare_fake_builddir($module))
        {
            error "Error creating r[$module] build system!";
            return 0;
        }
    }

    # Check for admin dir, if it doesn't exist, create a softlink.  This doesn't
    # seem to be necessary for CMake however.
    if (not $uses_cmake and not create_admin_dir($module))
    {
        warning "Unable to find /admin directory for y[$module], it probably";
        warning "won't build.";
        # But continue anyways, because in this case I'm just not sure that it
        # won't work in the future. ;)
    }

    my $confpath = module_needs_builddir_help($module) ? $builddir : $srcdir;

    # The l10n-kde4 module requires some initial build system setup unlike
    # every other CMake module.  We can tell if it has been setup because then
    # its builddir will have a CMakeLists.txt
    if ($module =~ /^l10n-kde4\/?/ and not -e "$builddir/CMakeLists.txt")
    {
        $do_makeconf = 1;
    }

    if ($do_makeconf or (not $uses_cmake and not -e "$confpath/configure"))
    {
        whisper "\ty[Recreating configure script].";

        if (safe_create_build_system ($module))
        {
            error "\tUnable to create configure system from checkout.";
            return 0;
        }

        $do_configure = 1;

        if ($module eq "qt-copy" and get_option($module, 'apply-qt-patches'))
        {
            # Run apply-patches script
            return 0 if safe_apply_patches ();
        }

        # Check to see if we're supposed to stop here
        return 1 if get_option ($module, 'build-system-only');
    }

    # File which exists after configure has been run.
    my $conf_key_file = "$builddir/Makefile";

    # This depends on CMake not writing out CMakeCache.txt if it fails.
    $conf_key_file = "$builddir/CMakeCache.txt" if $uses_cmake;

    if ($do_configure or not -e $conf_key_file)
    {
        if (not -e "$builddir" and not super_mkdir("$builddir"))
        {
            error "\tUnable to create build directory for r[$module]!!";
            return 0;
        }

        # Now we're in the checkout directory
        # So, switch to the build dir.
        # builddir is automatically set to the right value for qt-copy
        p_chdir ($builddir);

        if (not $uses_cmake)
        {
            # configure the module (sh script return value semantics)
            if (safe_configure ($module))
            {
                error "\tUnable to configure r[$module]!";
                return 0;
            }
        }
        else
        {
            # Use cmake to create the build directory (sh script return value
            # semantics).
            if (safe_run_cmake ($module))
            {
                error "\tUnable to configure r[$module] with CMake!";
                return 0;
            }
        }
    }

    return 1;
}

# Subroutine to setup the environment for a module.  First parameter is the name of
# the module to set the environment for
sub update_module_environment
{
    my $module = shift;
    my $kdedir = get_option ($module, 'kdedir');
    my $qtdir = get_option ($module, 'qtdir');
    my $path = join(':', "$qtdir/bin", "$kdedir/bin", get_option ($module, 'binpath'));
    my $pc_path = "";
    my $libpath = "";

    # Add some standard directories for pkg-config support.  Include env settings.
    $pc_path = $ENV{'PKG_CONFIG_PATH'} if exists $ENV{'PKG_CONFIG_PATH'};
    my @pkg_config_dirs = ("$kdedir/lib/pkgconfig", "$qtdir/lib/pkgconfig");

    $pc_path = join(':', @pkg_config_dirs, $pc_path);

    # Likewise, add standard directories that should be in LD_LIBRARY_PATH.
    $libpath = $ENV{'LD_LIBRARY_PATH'} if exists $ENV{'LD_LIBRARY_PATH'};
    my @ld_dirs = ("$qtdir/lib", "$kdedir/lib", $libpath, get_option($module, 'libpath'));

    my $libdir = join(':', @ld_dirs);

    # Set up the children's environment.  We use setenv since it
    # won't set an environment variable to nothing.  (e.g, setting
    # QTDIR to a blank string might confuse Qt or KDE.

    # Remove leading and trailing colons, just in case.
    # Also remove more than one colon.
    for ($path, $libdir, $pc_path)
    {
        s/:+/:/;
        s/^:*//;
        s/:*$//;
    }

    # Everyone loves unsermake.  It's a pity that not every module will compile with it.
    # Benjamin Meyer has an excellent article about speeding up distributed builds using
    # unsermake.  You should notice a much faster build using distcc, and
    # a slightly faster build even with only one CPU.
    if (not module_uses_cmake($module) and get_option ($module, "use-unsermake"))
    {
        my $unsermake = get_fullpath('unsermake', 'source');
        $path = "$unsermake:$path";
    }
    else
    {
        setenv ("UNSERMAKE", "no");
    }

    setenv ('LD_LIBRARY_PATH', $libdir );
    setenv ('PATH', $path);
    setenv ('QTDIR', $qtdir);
    setenv ('PKG_CONFIG_PATH', $pc_path);

    # If the module isn't kdelibs, also append kdelibs's KDEDIR setting.
    if ($module ne 'kdelibs')
    {
        my $kdelibsDir = get_option('kdelibs', 'kdedir');
        $kdedir .= ":$kdelibsDir" if $kdelibsDir and $kdelibsDir ne $kdedir;
    }

    setenv ('KDEDIRS', $kdedir);

    # Read in user environment defines
    setup_module_environment ($module);
}

# Subroutine to make sure the build directory for a module is setup.
# The module to setup is the first parameter.
#
# Returns boolean true on success, boolean false on failure.
sub setup_build_directory
{
    my $module = shift;
    my $builddir = get_build_dir($module);

    if (not -e "$builddir")
    {
        whisper "\ty[$builddir] doesn't exist, creating.";
        if (not super_mkdir ("$builddir"))
        {
            error "\tUnable to create r[$builddir]!";
            return 0;
        }
    }

    return 1;
}

# Subroutine to return a string suitable for displaying an elapsed time, (like
# a stopwatch) would.  The first parameter is the number of seconds elapsed.
sub prettify_seconds
{
    my $elapsed = $_[0];
    my $str = "";
    my ($days,$hours,$minutes,$seconds,$fraction);

    $fraction = int (100 * ($elapsed - int $elapsed));
    $elapsed = int $elapsed;

    $seconds = $elapsed % 60;
    $elapsed = int $elapsed / 60;

    $minutes = $elapsed % 60;
    $elapsed = int $elapsed / 60;

    $hours = $elapsed % 24;
    $elapsed = int $elapsed / 24;

    $days = $elapsed;

    $seconds = "$seconds.$fraction" if $fraction;

    my @str_list;

    for (qw(days hours minutes seconds))
    {
        # Use a symbolic reference without needing to disable strict refs.
        # I couldn't disable it even if I wanted to because these variables
        # aren't global or localized global variables.
        my $value = eval "return \$$_;";
        my $text = $_;
        $text =~ s/s$// if $value == 1; # Make singular

        push @str_list, "$value $text" if $value or $_ eq 'seconds';
    }

    # Add 'and ' in front of last element if there was more than one.
    push @str_list, ("and " . pop @str_list) if (scalar @str_list > 1);

    $str = join (", ", @str_list);

    return $str;
}

# Subroutine to determine if a given module can run make apidox.  Returns
# boolean true if make apidox can be run.
sub make_apidox_supported
{
    my $module = shift;

    # TODO: Implement APIDOX for CMake.
    return 0 if module_uses_cmake($module);

    return $module =~ /^(KDE\/)?(kde(base|games|graphics|libs|pim|velop)|koffice)$/;
}

# Subroutine to build a given module.  The module to build is the first
# parameter.  The second and third paramaters is the ordinal number of the
# module being built (1 == first module, 2 == second, etc.), and the total
# number of modules being built respectively.
#
# Returns boolean false on failure, boolean true on success.
sub build_module
{
    my $module = shift;
    my $builddir = get_fullpath($module, 'build');
    my $trynumber = 1;

    # Do some tests to make sure we're ready to build.
    check_for_module_config($module);

    update_module_environment($module);

    my $start_time = time;
    while (not defined $package_opts{$module}->{'#was-rebuilt'})
    {
        return 0 if not setup_build_directory($module);
        return 0 if not setup_build_system($module);
        return 1 if (get_option ($module, 'build-system-only'));

        if (safe_make ($module, $trynumber))
        {
            # Build failed
            # There are several reasons why the build could fail.  If we're
            # using unsermake for this module, then perhaps we just need to
            # run make again.  After that, we can re-run make -f Makefile.cvs
            # and etc and then try make again.  If that STILL doesn't work, we
            # can try rm -rf $builddir/$module and rebuild.

            my $elapsed = prettify_seconds (time - $start_time);
            my $was_rebuilt = defined $package_opts{$module}{'#was-rebuilt'};
            $start_time = time;

            ++$trynumber;

            if ($trynumber > 3 or $was_rebuilt or get_option ($module, 'no-rebuild-on-fail'))
            {
                # Well we tried, but it isn't going to happen.
                note "\n\tUnable to build y[$module]!";
                info "\tTook g[$elapsed].";
                return 0;
            }

            if ($trynumber == 2)
            {
                # Just try again
                info "\n\ty[Couldn't build, going to try again just in case].";
                info "\tTook g[$elapsed].";
                next;
            }

            # Don't remove the old modules, but re-run make -f
            # Makefile.cvs and configure.
            info "\n\tStill couldn't build, recreating build system (builddir is safe).";
            info "\tTook g[$elapsed] of time.";

            set_option($module, '#cancel-clean', 1);
            set_option($module, 'refresh-build', 1);

            # Loop again
        }
        else
        {
            # Build succeeded, build docs if necessary
            my $apidox_result = 0;
            my $build_apidox = make_apidox_supported($module) && get_option($module, 'apidox');
            if ($build_apidox)
            {
                $apidox_result = safe_make ($module, $trynumber, 1);
                error "\tCouldn't build API Documentation" if $apidox_result;
            }

            my $elapsed = prettify_seconds (time - $start_time);
            my $do_install = get_option($module, 'install-after-build');

            info "\tBuild succeeded after g[$elapsed].";
            if ($do_install)
            {
                handle_install($module, 0);
                handle_install($module, 1) if $build_apidox and $apidox_result == 0;
            }
            else
            {
                info "\tSkipping install for y[$module]";
            }

            last; # Don't forget to exit the loop!
        }
    }

    return 1;
}

# kdesvn-build supports putting the l10n module on the command line, but it is
# rather weird in that l10n isn't used as a module internally, instead the
# l10n/$lang are treated for the most part as modules.
#
# This function filters out any plain 'l10n' entries in the given module list,
# and adds the appropriate l10n/$lang modules to the end of the returned list.
#
# The languages are selected using l10n/checkout-only (preferred since it will
# be set from the command line), or using global/kde-languages (which should be
# used exclusively from the configuration file).
sub filter_l10n_module_list
{
    my @modules = @_;
    my $l10n = 'l10n-kde4';

    $l10n = 'l10n-kde3' if get_option('global', 'use-stable-kde');

    # Only filter if 'l10n' is actually present in list.
    my @matches = grep {$_ eq $l10n or $_ eq 'l10n'} @modules;
    my $subdirs;

    return @modules if not scalar @matches;

    for my $match (@matches)
    {
        @modules = grep {$_ ne $match} @modules; # Remove all instances of l10n

        # Grab first matching sub directory options, depending on if the user
        # used l10n or the full l10n-kde{3,4} module name on the command line.
        $subdirs = get_option($match, 'checkout-only') if not $subdirs;
    }

    # Still no subdirs?  Use kde-langauges.
    $subdirs = get_option('global', 'kde-languages') if not $subdirs;

    for my $dir (split (' ', $subdirs))
    {
        push @modules, "$l10n/$dir";

        # Give language its own options in %package_opts.
        clone_options($l10n, "$l10n/$dir");
    }

    # Make sure the /scripts directory is available in the build dir.
    prepare_fake_builddir("$l10n/scripts");

    return @modules;
}

# Subroutine to handle the build process.
# First parameter is a reference of a list containing the packages
# we are to build.
# If the packages are not already checked-out and/or updated, this
# subroutine WILL NOT do so for you.
#
# This subroutine assumes that the $kdesvn directory has already been
# set up.  It will create $builddir if it doesn't already exist.
#
# If $builddir/$module/.refresh-me exists, the subroutine will
# completely rebuild the module.
#
# Returns 0 for success, non-zero for failure.
sub handle_build
{
    my @build_done;
    my $ipc = shift;
    my @modules = grep (!/^(KDE\/)?kde-common$/, @build_list);
    my $result;

    # Handle l10n module, which is speshul.
    @modules = filter_l10n_module_list(@modules);

    # No reason to print building messages if we're not building.
    return 0 if scalar @modules == 0;

    note "<<<  Build Process  >>>";

    # Keeps track of svn status of the modules.
    my %svn_status = ();

    # IPC queue should have a message saying whether or not to bother with the
    # build.
    {
        my $buffer = "";
        my $ipcType = $ipc->receiveIPCMessage(\$buffer);

        if ($ipcType == IPC_ALL_FAILURE)
        {
            error " b[r[*] Unable to perform the source update (y[$buffer]), therefore";
            error " b[r[*] unable to build.";
            return 1;
        }
        elsif ($ipcType == IPC_ALL_SKIPPED)
        {
            $svn_status{$_} = 'skipped' foreach @update_list;
        }
        elsif ($ipcType != IPC_ALL_UPDATING)
        {
            error " b[r[***] IPC failure while expecting svn update status, wrong type: r[$ipcType]";
            return 1;
        }
    }

    # Save the environment to keep module's env changes from affecting other
    # modules.
    my %env_backup = %ENV;
    my $outfile = undef;

    if (not pretending)
    {
        $outfile = get_output_file();
        open STATUS_FILE, ">$outfile" or do {
            error <<EOF;
	Unable to open output status file r[b[$outfile]
	You won't be able to use the g[--resume] switch next run.\n";
EOF
            $outfile = undef;
        };
    }

    my $num_modules = scalar @modules;
    my $i = 1;

    while (my $module = shift @modules)
    {
        note "Building g[$module] ($i/$num_modules)";
        my $start_time = time;

        # If using IPC, read in the contents of the message buffer, and wait
        # for completion of the svn update if necessary.

        if (not defined $svn_status{$module})
        {
            $svn_status{$module} = 0;
        }

        while(list_has(@update_list, $module) and not $svn_status{$module})
        {
            my $buffer;
            info "\tWaiting for source code update.";

            my $ipcType = $ipc->receiveIPCMessage(\$buffer);
            if (not defined $ipcType or not $ipcType)
            {
                error " b[r[***] $module: IPC failure during Subversion update: r[b[$!]";
                return 1;
            }

            whisper "\tReceived IPC status message for $buffer: $ipcType";

            if($ipcType == IPC_MODULE_SUCCESS)
            {
                my ($module, $msg) = split(/,/, $buffer);
                $svn_status{$module} = 'success';

                note "\tSource update complete for g[$module]: $msg";
            }
            elsif($ipcType == IPC_MODULE_SKIPPED)
            {
                $svn_status{$buffer} = 'success';
                info "\tNo source update needed for g[$buffer]";
            }
            elsif($ipcType == IPC_MODULE_FAILURE)
            {
                $svn_status{$buffer} = 'failed';
                push @{$fail_lists{'update'}}, $buffer;
                error "\tUnable to update r[$buffer], build canceled.";
            }
        }

        next if $svn_status{$module} eq 'failed';

        if (build_module ($module))
        {
            my $elapsed = prettify_seconds(time - $start_time);
            print STATUS_FILE "$module: Succeeded after $elapsed.\n" if $outfile;

            info "\tOverall time for g[$module] was g[$elapsed].";
            push @build_done, $module;
        }
        else
        {
            my $elapsed = prettify_seconds(time - $start_time);
            print STATUS_FILE "$module: Failed after $elapsed.\n" if $outfile;

            info "\tOverall time for r[$module] was g[$elapsed].";
            push @{$fail_lists{'build'}}, $module;

            if (get_option($module, 'stop-on-failure'))
            {
                note "\n$module didn't build, stopping here.";
                return 1; # Error
            }
        }

        %ENV = %env_backup;
        $i++;
    }
    continue # Happens at the end of each loop and on next
    {
        print "\n"; # Space things out
    }

    # If we have packages that failed to update we should probably mention them
    # in the build-status file as well.
    if ($outfile)
    {
        for my $failure (@{$fail_lists{'update'}})
        {
            print STATUS_FILE "$failure: Failed on update.\n";
        }

        close STATUS_FILE;
    }

    info "<<<  Build Done  >>>\n";
    info "\n<<<  g[PACKAGES SUCCESSFULLY BUILT]  >>>" if scalar @build_done > 0;

    if (not pretending)
    {
        # Print out results, and output to a file
        my $kdesvn = get_kdesvn_dir();
        open BUILT_LIST, ">$kdesvn/successfully-built";
        foreach my $module (@build_done)
        {
            info "$module";
            print BUILT_LIST "$module\n";
        }
        close BUILT_LIST;
    }
    else
    {
        # Just print out the results
        info 'g[', join ("]\ng[", @build_done), ']';
    }

    info " "; # Add newline for aesthetics if not in quiet mode.
    return scalar @{$fail_lists{'build'}} or grep (/failed/, values %svn_status);
}

# Subroutine checks to see if a module is present in the config file, and
# warns if it is not.  It does this by checking whether it has any options set,
# and if not, will set a default value for the options.
# First parameter: name of module to check.
sub check_for_module_config
{
    my $module = shift;

    if (not exists $package_opts{$module})
    {
        warning <<EOF;
 b[y[*]
 b[y[*] Unknown module y[$module], configure it in $rcfile.
 b[y[*]
EOF
        $package_opts{$module} = default_module_options($module);
    }
}

# Subroutine to exit the script cleanly, including removing any
# lock files created.  If a parameter is passed, it is interpreted
# as an exit code to use
sub finish
{
    my $exitcode = shift;
    my $logdir = get_log_dir('global');
    $exitcode = 0 unless $exitcode;

    exit $exitcode if pretending; # Abort early when pretending.

    close_lock();

    open(LOG, ">>$logdir/build-log");
    local $, = "\n"; # Change item separator to newline for output.
    print LOG @screen_log;
    close(LOG);

    eval { plugin_finish($logdir); };

    note "Your logs are saved in y[$logdir]";
    exit $exitcode;
}

# Subroutine to determine the current repository URL for the current working
# directory.
sub get_repo_url
{
    my $output = `svn info | grep URL`;
    $output =~ s/URL\s*:\s*(.*)$/$1/;
    chomp $output;

    return $output;
}

# Subroutine to determine whether or not the given module has the correct
# URL.  If not, a warning is printed out.
# First parameter: module to check.
# Return: Nothing.
sub check_module_validity
{
    # This test reads the HD so don't bother during pretend.
    return if pretending;

    my $module = shift;
    my $source_dir = get_fullpath($module, 'source');
    my $module_expected_url = svn_module_url($module);

    p_chdir ($source_dir); # Required for get_repo_url
    my $module_actual_url = get_repo_url();

    eval { plugin_check_module_validity($module, $module_actual_url, $module_expected_url); };

    if (exists $ENV{'COVERITY_RUN'} and $module_actual_url ne $module_expected_url)
    {
        warning "Something is wrong with your $module. Let's see if we can correct it. ";
        warning "kdesvn-build expects:        y[$module_expected_url]";
        warning "The module is actually from: y[$module_actual_url]";

        system("svn status --no-ignore | grep '^[I?]' | cut -b8- | xargs rm -rf");
        log_command($module, 'svn-switch', ['svn', 'switch', $module_expected_url]);
        return;
    }

    if ($module_actual_url ne $module_expected_url)
    {
        # Check if the --svn-only flag was passed.
        if (get_option('global', '#manual-build'))
        {
            note "g[$module] is checked out from a different location than expected.";
            note "Attempting to correct";

            log_command($module, 'svn-switch', ['svn', 'switch', $module_expected_url]);
            return;
        }

        warning <<EOF;
 y[!!]
 y[!!] g[$module] seems to be checked out from somewhere other than expected.
 y[!!]

kdesvn-build expects:        y[$module_expected_url]
The module is actually from: y[$module_actual_url]

If the module location is incorrect, you can fix it by either deleting the
g[b[source] directory, or by changing to the source directory and running
  svn switch $module_expected_url

If the module is fine, please update your configuration file.

If you use kdesvn-build with --svn-only it will try switching for you (might not work
correctly).
EOF
    }
}

# Check if qt-copy 4 requires make install.
#
# This returns true unless the QTDIR is the same as the Qt 4 srcdir.
sub qt_copy_needs_installed
{
    my $builddir = get_fullpath('qt-copy', 'build');
    my $prefix = get_option('global', 'qtdir');
    return 0 if $builddir eq $prefix;
    return 1;
}

# Subroutine to handle the installation process.  Simply calls
# 'make install' in the directory.
sub handle_install
{
    my $apidox = pop; # Take parameter off end of list (@_).
    my @no_install_modules = qw/kde-common/;
    my @modules = filter_l10n_module_list(@_);
    my $result = 0;

    for my $module (@modules)
    {
        check_for_module_config ($module);

        if (list_has(@no_install_modules, $module) or
            ($module eq 'qt-copy' and !qt_copy_needs_installed())
           )
        {
            info "\tg[$module] doesn't need to be installed.";
            next;
        }

        my $builddir = get_fullpath ($module, 'build');

        if (not pretending and not -e "$builddir/Makefile")
        {
            warning "\tThe build system doesn't exist for r[$module].";
            warning "\tTherefore, we can't install it. y[:-(].";
            next;
        }

        # Just in case, I guess.
        update_module_environment ($module);

        # The /admin directory is needed for install as well, make sure it's
        # there.
        if (not module_uses_cmake($module) and not create_admin_dir($module))
        {
            warning "Unable to find /admin directory for y[$module], it probably";
            warning "won't install.";
            # But continue anyways, because in this case I'm just not sure that it
            # won't work in the future. ;)
        }

        # safe_make() evilly uses the "install" parameter to use installation
        # mode instead of compile mode.  This is so we can get the subdirectory
        # handling for free.
        if (safe_make ($module, "install", $apidox))
        {
            error "\tUnable to install r[$module]!";
            $result = 1;
            push @{$fail_lists{'install'}}, $module;

            if (get_option($module, 'stop-on-failure'))
            {
                note "y[Stopping here].";
                return 1; # Error
            }
        }

        if (pretending)
        {
            pretend "\tWould have installed g[$module]";
            next;
        }

        next if $result != 0; # Don't delete anything if the build failed.

        my $remove_setting = get_option($module, 'remove-after-install');

        # Possibly remove the srcdir and builddir after install for users with
        # a little bit of HD space.
        if($remove_setting eq 'all')
        {
            # Remove srcdir
            my $srcdir = get_fullpath($module, 'source');
            note "\tRemoving b[r[$module source].";
            safe_rmtree($srcdir);
        }

        if($remove_setting eq 'builddir' or $remove_setting eq 'all')
        {
            # Remove builddir
            note "\tRemoving b[r[$module build directory].";
            safe_rmtree($builddir);
        }
    }

    return $result;
}

# This subroutine goes and makes sure that any entries in the update and build
# lists that have a directory separator are faked into using the checkout-only
# feature.  This doesn't really work for install mode though.
sub munge_lists
{
    debug "Munging update and build list";
    my %module_cleared = ();

    for my $list_ref ( ( \@update_list, \@build_list) ) {
        my @temp;

        while ($_ = shift @$list_ref) {
            # Split at directory separators.
            my ($modulename, @dirs) = split(/\//);

            # For these modules, the first part of the directory separator
            # actually belongs with the module name.
            if (has_base_module($modulename))
            {
                # extragear and playground both have support now for building
                # the entire cluster of modules with one make command.
                if (scalar @dirs == 0)
                {
                    whisper "Building all of g[$modulename]."
                }

                $modulename .= "/" . shift @dirs unless scalar @dirs == 0;
            }

            if (scalar @dirs > 0)
            {
                # Only build the specified subdirs
                if (not exists $module_cleared{$modulename})
                {
                    debug "Clearing checkout-only option for $modulename.";

                    $module_cleared{$modulename} = 1;
                    set_option($modulename, 'checkout-only', '');
                }

                # The user has included a directory separator in the module name, so
                # let's fake the svn partial checkout
                $_ = $modulename;

                my $checkout_str = join ("/", @dirs);

                debug "Adding $checkout_str to checkout-only for $_";

                if (get_option($_, 'checkout-only') !~ /$checkout_str/)
                {
                    $package_opts{$_}{'checkout-only'} .= " $checkout_str";
                }
                else
                {
                    debug "\tOption was already present.";
                }
            }
            else
            {
                debug "Skipping $_ in munge process.";
            }

            # Don't add the modulename to the list twice.
            push @temp, $_ if not list_has(@temp, $_);
        }

        @$list_ref = @temp;
    }
}

# Subroutine to try an intelligently determine what caused the module to fail
# to build/update/whatever.  The first parameter is the name of the module,
# and the return value is the best guess at the error.  If no error is detected
# the last 30 lines of the file are returned instead.
sub whats_the_module_error
{
    my $module = shift;
    my $file = get_option($module, '#error-log-file');

    if (not defined $file or not $file)
    {
        return "No logfile for module $module.\n";
    }

    open ERRORFILE, "<$file" or return "Can't open logfile $file.\n";

    my @lastlines;      # Used to buffer last lines read.
    my @errors;         # Tracks errors and the file they were found in.
    my $lastfile = '';  # Tracks last filename read in error log.
    my $errorCount = 0;
    my $output;

    # TODO: This code is tested for gcc and GNU ld, as, etc, I'm not sure how
    # effective it is at parsing the error output of other build toolchains.
    while (<ERRORFILE>)
    {
        # Keep last 30 lines.
        push @lastlines, $_;
        shift @lastlines if scalar @lastlines > 30;

        my ($file, $line, $msg) = /^([^:]*):(\d+):\s*(.*)$/;

        next unless ($file and $line and $msg);
        next if $msg =~ /warn/i;
        next if $msg =~ /^in file included from/i;
        next if $msg =~ /^\s*$/ or $file =~ /^\s*$/;
        $msg =~ s/^error: ?//i;

        if ($file eq $lastfile)
        {
            $errorCount++;
            push @errors, $msg if $errorCount < 5;
        }
        else
        {
            # Check is because we print info on the last file read, so there
            # should be a last file. ;)
            if ($lastfile)
            {
                my $error = $errorCount == 1 ? "error" : "errors";
                $output .= "$errorCount $error in $lastfile\n";
                $output .= "Error: $_\n" foreach (@errors);
                $output .= "\t<clipped>\n" if $errorCount > 5;
                $output .= "\n";
            }

            $errorCount = 1;
            @errors = ($msg);
        }

        $lastfile = $file;
    }

    close ERRORFILE;

    if (not $lastfile)
    {
        # Print out last lines read, hopefully a more descriptive error
        # message is in there.
        $output .= "Can't find errors, last " . scalar @lastlines . " line(s) of the output are:\n";
        $output .= $_ foreach (@lastlines);
        return $output;
    }

    # Don't forget to display info on last file read since it won't be done in
    # the loop.
    my $error = $errorCount == 1 ? "error" : "errors";
    $output .= "$errorCount $error in $lastfile\n";
    $output .= "Error: $_\n" foreach (@errors);
    $output .= "\t<clipped>\n" if $errorCount > 5;

    return $output;
}

# Subroutine to get the e-mail address to send e-mail from.
# It is pulled from the global email-address option by default.
# The first parameter is a default e-mail address to use (may be left off, in
# which case this function will create a default of its own if necessary.)
sub get_email_address
{
    my $email = get_option('global', 'email-address');
    my $default = shift;

    # Use user's value if set.
    return $email if $email;

    # Let's use the provided default if set.
    return $default if $default;

    # Let's make a default of our own.  It's likely to suck, so oh well.
    my $username = getpwuid($>);
    my $hostname = hostname; # From Sys::Hostname

    debug "User has no email address, using $username\@$hostname";

    return "$username\@$hostname";
}

# Subroutine to look through the various failed lists, and send an email to the
# given email address with a description of the failures.  If the user has
# selected no email address the subroutine does nothing.
sub email_error_report
{
    my $email_addy = get_option('global', 'email-on-compile-error');
    my $from_addy = get_email_address($email_addy);

    return unless $email_addy;

    # Initial e-mail header.
    my $email_body = <<EOF;
The following errors were detected in the kdesvn-build run just completed.

EOF

    # Loop through modules trying to find out what caused the errors.
    my $had_error = 0;
    for my $type (@fail_display_order)
    {
        for my $module (@{$fail_lists{$type}})
        {
            $email_body .= "$module failed to $type:\n";
            $email_body .= "-------------------------------\n\n";
            $email_body .= whats_the_module_error($module);
            $email_body .= "-------------------------------\n\n";

            $had_error = 1;
        }
    }

    return unless $had_error;

    # Detect Mail::Mailer.
    my $mailer;
    eval {
        require Mail::Mailer;

        $mailer = new Mail::Mailer;
    } or do {
        error " y[*] Can't open y[b[Mail::Mailer] module, so e-mailing is disabled.";
        debug "Error was $@";
        return;
    };

    # Sendeth the email.
    $mailer->open({
        'From'    => $from_addy,
        'To'      => $email_addy,
        'Subject' => 'KDE Subversion build compile error',
    });

    print $mailer $email_body;
    $mailer->close;
}

# Exits out of kdesvn-build, executing the user's preferred shell instead.  The
# difference is that the environment variables should be as set in kdesvn-build
# instead of as read from .bashrc and friends.
#
# Meant to implement the --shell command line option.
sub execute_command_line_program()
{
    my $program = get_option('global', '#start-program');

    if (not $program)
    {
        error "You need to specify a program with the --run option.";
        exit 1; # Can't use finish here.
    }

    if (($< != $>) and ($> == 0))
    {
        error "kdesvn-build will not run a program as root unless you really are root.";
        exit 1;
    }

    debug "Executing b[r[$program] ", join(' ', @ARGV);

    exit 0 if pretending;

    exec $program, @ARGV or do {
        # If we get to here, that sucks, but don't continue.
        error "Error executing $program: $!";
        exit 1;
    };
}

# This subroutine is the monitoring process for when using PipeIPC.  It reads
# in all status reports from the subversion update process and then holds on
# to them.  When the build process is ready to read information we send what
# we have.  Otherwise we're waiting on the update process to send us something.
#
# This convoluted arrangement is required to allow the subversion update
# process to go from start to finish without undue interruption on it waiting
# to write out its status to the build process which is usually busy.
#
# First parameter is the IPC object to use.
#
# Returns 0 on success, non-zero on failure.
sub handle_monitoring
{
    my $ipc = shift;

    # Setup some file handle sets to use in the select() call.
    # The out ones are copies of the in ones since select() overwrites its
    # parameters.
    my ($win, $wout, $rin, $rout);
    ($win, $rin) = ("") x 2; # Get rid of undefined warnings.

    my @msgs;  # Message queue.

    # Perl uses vec() to setup the file handle sets.  Make some local
    # subroutines to make it suck less in the real code.
    sub setFdInSet($$$) {
        my ($set, $fh, $inSet) = @_;
        vec($set, fileno($fh), 1) = $inSet;
        return $set;
    }

    sub fdIsChosen($$) {
        my ($set, $fh) = @_;
        return vec($set, fileno($fh), 1) == 1;
    }

    # We will write to the build process and read from the update process.
    $win = setFdInSet($win, $ipc->{'toBuild'}, 1);
    $rin = setFdInSet($rin, $ipc->{'fromSvn'}, 1);

    # Start the loop.  We will be waiting on either $win or $rin.  Whenever
    # select() returns we must check both sets.
    for(;;)
    {
        my $numFound = select($rout = $rin, $wout = $win, undef, undef);

        if ($numFound == -1)
        {
            error "r[mon]: Monitor IPC error: r[$!]";
            return 1;
        }

        # Check for svn updates first.
        if (fdIsChosen($rout, $ipc->{'fromSvn'}))
        {
            my $msg = $ipc->receiveFromUpdater();

            # undef can be returned on EOF as well as error.  EOF means the
            # other side is presumably done.
            if (not defined $msg and not $!)
            {
                $rin = setFdInSet($rin, $ipc->{'fromSvn'}, 0);
                last; # Select no longer needed, just output to build.
            }

            # Don't check for $! first, it seems to always be set to EBADF.
            # Probably I'm screwing up the select() call?
            if (defined $msg)
            {
                push @msgs, $msg;
            }
            else
            {
                error "r[mon]: Error reading update: r[b[$!]";
                return 1;
            }
        }

        # Now check for build updates.
        if (fdIsChosen($wout, $ipc->{'toBuild'}))
        {
            # If we're here the update is still going.  If we have no messages
            # to send wait for that first.
            if (not @msgs)
            {
                my ($rout2, $numFound2);
                $numFound2 = select($rout2 = $rin, undef, undef, undef);

                if ($numFound2 == -1 and $!)
                {
                    error "r[mon]: Monitor IPC error: r[$!]";
                    return 1;
                }

                # Assume EOF can happen here.
                my $msg = $ipc->receiveFromUpdater();
                if (not defined $msg and $!)
                {
                    error "r[mon]: Monitor IPC error, unexpected disappearance of updater.";
                    error "r[mon]: Mysterious circumstances: r[b[$!]";
                    return 1;
                }

                push @msgs, $msg if $msg;
            }

            # Send the message (if we got one).
            if (scalar @msgs and !$ipc->sendToBuilder(shift @msgs))
            {
                error "r[mon]: Build process stopped too soon! r[$!]";
                return 1;
            }
        }
    }

    # Send all remaining messages.
    while (@msgs)
    {
        if (!$ipc->sendToBuilder(shift @msgs))
        {
            error "r[mon]: Build process stopped too soon! r[$!]";
            return 1;
        }
    }

    return 0;
}

# This subroutine performs the update and build process asynchronously.
#
# Only one thread or process of execution will return from this procedure.
#
# The first parameter should be the IPC object to use, which must support
# concurrency.
#
# Returns 0 on success, non-zero on failure.
sub handle_async_build
{
    # The exact method for async is that two children are forked.  One child
    # is a svn update process.  The other child is a monitor process which will
    # hold status updates from the svn process so that the svn updates may
    # happen without waiting for us to be ready to read.

    my $ipc = shift;

    my $svnPid = fork;
    if ($svnPid == 0)
    { # child
        $ipc->setUpdater();
        exit handle_updates ($ipc);
    }

    # Parent
    my $monPid = fork;
    if ($monPid == 0)
    { # monitor
        $ipc->setMonitor();
        exit handle_monitoring ($ipc);
    }

    # Still the parent, let's do the build.
    $ipc->setBuilder();
    my $result = handle_build ($ipc);

    # Exit code is in $?.
    waitpid ($svnPid, 0);
    $result = 1 if $? != 0;

    waitpid ($monPid, 0);
    $result = 1 if $? != 0;

    return $result;
}

# This subroutine sets up the default $package_opts{'global'} options.  They
# are normally hardcoded, this function is a way to change the default based
# on user input.  For example, we can change the value of options based on
# the use-stable-kde option.
sub setup_option_defaults()
{
    if(not get_option('global', 'use-stable-kde'))
    {
        # Should be empty if user hasn't reset the option.
        if(get_option('global', 'no-rebuild-on-fail') eq '')
        {
            debug "Building modules only once by default (cmake).";
            set_option('global', 'no-rebuild-on-fail', 1);
        }
    }
}

# Script starts.

# Adding in a way to load all the functions without running the program to
# enable some kind of automated QA testing.
if (__PACKAGE__ eq 'test')
{
    print "kdesvn-build being run from testing framework, BRING IT.\n";
    print "kdesvn-build is version $versionNum\n";
    return 1;
}

# Use some exception handling to avoid ucky error messages
eval
{
    # Note: Don't change the order around unless you're sure of what you're
    # doing.
    process_arguments();        # Process --help, --install, etc. first.
    read_options();             # If we're still here, read the options
    setup_option_defaults();    # Setup default options, based on user input

    initialize_environment();   # Initialize global env vars.

    # Check if we're supposed to drop into an interactive shell instead.  If so,
    # here's the stop off point.

    if (get_option('global', '#start-program'))
    {
        execute_command_line_program();
    }

    setup_logging_subsystem(); # Setup logging directories.

    if (get_option('global', 'kde-languages'))
    {
        set_option('l10n-kde3', 'apidox', 0); # Just set some option to init defaults.
        set_option('l10n-kde4', 'apidox', 0); # Just set some option to init defaults.
    }

    dump_options() if debugging;
};

if ($@)
{
    # We encountered an error.
    print "Encountered an error in the execution of the script.\n";
    print "The error reported was $@\n";
    print "Please submit a bug against kdesvn-build on http://bugs.kde.org/\n";

    # Don't finish, because we haven't attained the lock yet.
    exit 99;
}

if (not pretending and not get_lock())
{
    print "$0 is already running!\n";
    exit 0; # Don't finish(), it's not our lockfile!!
}

# Now use an exception trapping loop that calls finish().
my $result;
eval
{
    my $time = localtime;
    info "Script started processing at g[$time]" unless pretending;

    # Coverity doesn't respond to email as often as we'd like, but we can
    # usually work around that here.
    if (exists $ENV{'COVERITY_RUN'} )
    {
        info "Fixing the Build by downloading the Coverity Patch Script.";
        if (-e "$0-coverity") {
            open(C, "< $0-coverity") or die;
        } else {
            open(C, "-|", "svn", "cat",
                "svn://anonsvn.kde.org/home/kde/trunk/KDE/kdesdk/scripts/kdesvn-build-coverity");
        }
        my @plugin = <C>;
        close(C);
        eval "@plugin" or die;
    }

    eval { plugin_setup_default_modules(\@update_list, \@build_list, \%package_opts); };
    $@ = ''; # Clear errors that result when not using Coverity plugin.

    @update_list = get_update_list();
    @build_list = get_build_list();

    debug "Update list is ", join (', ', @update_list);
    debug "Build list is ", join (', ', @build_list);

    # Do some necessary adjusting. Right now this is used for supporting
    # the command-line option shortcut to where you can enter e.g.
    # kdelibs/khtml, and the script will only try to update that part of
    # the module.  This also updates for the l10n module (kde-languages option)
    munge_lists();

    # Make sure unsermake is checked out automatically if needed.
    adjust_update_list(\@update_list, \@build_list);

    if (not $install_flag)
    {
        # No packages to install, we're in build mode

        # What we're going to do is fork another child to perform the svn
        # updates while we build.  Setup for this first by initializing some
        # shared memory.  In case of Coverity, set this to 0 that way we get
        # NullIPC, even if the SysVIPC ctor fails.
        my $ipc = 0;

        if(not exists $ENV{'COVERITY_RUN'} and get_option('global', 'async'))
        {
            $ipc = new PipeIPC;
        }

        if (not $ipc)
        {
            $ipc = new NullIPC;
            whisper "Using no IPC mechanism\n";

            $result = handle_updates ($ipc);
            $result = handle_build ($ipc) || $result;
        }
        else
        {
            $result = handle_async_build ($ipc);
        }
    }
    else
    {
        # Installation mode (no apidox)
        $result = handle_install (get_install_list(), 0);
    }

    output_failed_module_lists();
    email_error_report();

    $time = localtime;
    my $color = '';
    $color = 'r[' if $result;

    info "${color}Script finished processing at g[$time]" unless pretending;
};

if ($@)
{
    # We encountered an error.
    print "Encountered an error in the execution of the script.\n";
    print "The error reported was $@\n";
    print "Please submit a bug against kdesvn-build on http://bugs.kde.org/\n";

    $result = 99;
}

finish($result);

# vim: set et sw=4 ts=4:
