#!/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 - 2007 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<--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 'strftime';
use File::Find; # For our lndir reimplementation.
use File::Basename;
use Sys::Hostname;
use Errno qw(:POSIX);

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

my $versionNum = '1.4.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"    => "",
    "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
  }
);

# 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 subdirecty 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";

    # 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/-i18n -common accessibility
            addons admin artwork base bindings edu games graphics libs
            multimedia network nonbeta pim sdk toys utils webdev/), 'kdevelop',
            'konstruct');

    # 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);

    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');
    }

    # 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 kdenonbeta kdesupport koffice
        playground qt-copy valgrind KDE kdereview www l10n);

    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.
    # TODO: This probably can result in a few dozen unnecessary calls to
    # unlink and symlink, fix this.
    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.
# First parameter is the module, all remaining parameters are subdirectories
# to check out.
#
# Returns 0 on success, non-zero on failure.
sub update_module_subdirectories
{
    my $module = shift;
    my $result;

    # If we have elements in @path, download them now
    for my $dir (@_)
    {
        info "\tUpdating g[$dir]";
        $result = run_svn($module, "svn-up-$dir", [ 'svn', 'up', $dir ]);
        return $result if $result;
    }

    return 0;
}

# 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/kdeaddons -> kdeaddons

    # 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.
# The first parameter is the module to checkout (including extragear and
# playground modules), all remaining parameters are subdirectories of the
# module to checkout.
# Returns 0 on success, non-zero on failure.
sub checkout_module_path
{
    my ($module, @path) = @_;
    my %pathinfo = get_module_path_dir($module, 'source');
    my $result;
    my @args;

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

    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 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 0;
        }
    }

    push @args, ('svn', 'co', '--non-interactive');
    push @args, '-N' if scalar @path;
    push @args, svn_module_url($module);
    push @args, $pathinfo{'module'};

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

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

    return update_module_subdirectories($module, @path);
}

# Update a module that has already been checked out, along with any
# subdirectories the user desires.
# The first parameter is the module to checkout (including extragear and
# playground modules), all remaining parameters are subdirectories of the
# module to checkout.
# Returns 0 on success, non-zero on failure.
sub update_module_path
{
    my ($module, @path) = @_;
    my $fullpath = get_fullpath($module, 'source');
    my $result;
    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]";

    $result = run_svn($module, 'svn-up', \@args);

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

        return $result if $result;

        info "\tCleanup complete.";
        # Now try again.

        $result = run_svn($module, 'svn-up-2', \@args);
    }

    return $result if $result;

    # 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";

    return update_module_subdirectories($module, @path);
}

# 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';
    return 1;
}

# Subroutine that checks whether qt-copy is Qt 3 or below.  If true, then you
# can assume that the qt-copy module is from Qt 3.  If false, Qt 4 or better.
#
# This works by checking if some Qt 3-specific files exist.  If they don't, we
# assume Qt 4+.
#
# Naturally this requires that the source is already downloaded for this
# function to work.  However, if pretend mode is set we'll check if the branch
# or tag option is set to 3.foo, if so we assume the user will be downloading
# qt 3.
sub is_qt_copy_qt3
{
    my $source_dir = get_fullpath('qt-copy', 'source');

    if (pretending)
    {
        return 1 if get_option('qt-copy', 'branch') =~ /^3\./;
        return 1 if get_option('qt-copy', 'tag') =~ /^3\./;
        return 0;
    }

    # This file seems to be part of (only) Qt 3's documentation.
    return -e "$source_dir/doc/html/y2k.html";
}

# 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.
#
# 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',
        'kdeaddons'        => '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($module, 'use-stable-kde') and exists $branched_modules{$module})
    {
        return $branched_modules{$module};
    }

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

# Subroutine to test if the given module has been given a custom branch by the
# user.  This function is needed since there are at least four options that can
# control this: branch, tag, override-url, and module-base-path.
#
# NOTE: This test is performed merely by checking if any of the appropriate
# options are already set.  So if kdesvn-build sets an option internally, this
# function will still return true.
#
# First parameter is the module in question.
# Returns boolean true if the module has a custom branch, false otherwise.
sub module_has_custom_branch
{
    my $module = shift;
    my $branch = get_option($module, 'branch');

    # branch is kind of special.  It's a custom branch if the value is set, but is not
    # set to the default branch value.

    return (($branch and $branch ne default_module_branch($module)) or
            get_option($module, 'override-url') or
            get_option($module, 'tag') or
            get_option($module, 'module-base-path')
            );
}

# 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 use the configuration file generator to guide you through the process of
creating a config file to customize your kdesvn-build process.

HOME
}

# This subroutine assigns the appropriate options to %package_opts and the
# update and build lists to build a default set of modules.
sub setup_default_modules()
{
    @update_list = qw(qt-copy kdesupport kdelibs kdepimlibs kdebase kdeartwork
                      kdemultimedia kdepim kdeutils kdegraphics kdegames
                      kdetoys kdeedu kdeaddons kdenetwork kdeutils);

    @build_list = @update_list;

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

    for my $i (@update_list) {
        if (not exists $package_opts{$i})
        {
            $package_opts{$i} = { }; # Set up defaults
            $package_opts{$i}{'set-env'} = { };
        }
    }

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

# Subroutine sets up the default branch option for several modules when the
# use-stable-kde option is enabled, in order to build KDE 3.5 by default.
# branch options specified in the config file should still be chosen over these
# defaults.
sub setup_module_branches
{
    for my $module (@update_list)
    {
        unless (module_has_custom_branch($module))
        {
            my $default_branch = default_module_branch($module);
            debug "Setting $module to branch '$default_branch'";
            set_option ($module, 'branch', $default_branch);
        }
    }
}

# 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;

    # Make sure we acknowledge that we read the module name in from the
    # file.
    if (not defined $package_opts{$module})
    {
        $package_opts{$module} = {
            'set-env' => { }
        };
    }

    # 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.

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.
sub module_needs_builddir_help
{
    my $module = shift;
    my @module_help_list = qw/kdebindings valgrind/;

    # qt-copy special case to support the lndir() hack for Qt 3.
    if ($module eq 'qt-copy' and is_qt_copy_qt3())
    {
        return 1;
    }

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

    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');
            $logfile = "No log file" unless $logfile;
            $logfile =~ s|$homedir|~|;

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

# 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()
{
    return if pretending;

    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
    if (not exists $package_opts{$module})
    {
        $package_opts{$module} = {
            'set-env' => { }
        };
    }

    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;
            };

            # 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($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;
                    }

                    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;

}

# 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.
    $SIG{'HUP'} = \&quit_handler;
    $SIG{'INT'} = \&quit_handler;
    $SIG{'QUIT'} = \&quit_handler;
    $SIG{'ABRT'} = \&quit_handler;
    $SIG{'TERM'} = \&quit_handler;
    $SIG{'PIPE'} = \&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 is using unsermake.  If so, we need to check if kdenonbeta 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 kdenonbeta/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)
            and not blacklisted($module, 'use-unsermake'))
        {
            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('kdenonbeta', 'manual-update');

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

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

            # kdenonbeta isn't being downloaded by the user.
            unshift (@{$list_ref}, 'kdenonbeta');
            $package_opts{'kdenonbeta'} = {
                'manual-build'   => 'true',
                'checkout-only'  => 'unsermake',
            };
        }
        else
        {
            my $checkouts = get_option('kdenonbeta', 'checkout-only');

            if ($checkouts !~ /\bunsermake\b/)
            {
                # kdenonbeta is being checked out, but the user has
                # excluded unsermake.
                set_option('kdenonbeta', 'checkout-only', "$checkouts unsermake");
            }
        }
    }
}

# 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;

    if (get_option('global', 'kde-languages') and not get_option('l10n', 'manual-update'))
    {
        push @update_list, 'l10n'; # Add l10n 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');

    # 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 explicity 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;
        return 0 if not mkdir ($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;

    whisper "Not building $module";

    # 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.
#
# Returns 0 on success, non-zero on error.
sub handle_updates
{
    my $update_ref = shift;
    my $kdesvn = get_kdesvn_dir();
    my $result = 0;
    my $module;

    # No reason to print out the text if we're not doing anything.
    return 0 if get_option ('global', 'no-svn');
    return 0 if scalar @$update_ref == 0;

    return 1 if (not check_for_ssh_agent());

    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]!";
            @build_list = (); # Clear out the build list, since we can't build.
            $install_flag = 0; # Can't install either.
            return 1;
        }
    }

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

    foreach $module (@{$update_ref})
    {
        check_for_module_config ($module);

        next if get_option($module, 'no-svn');

        my @options = split(' ', get_option($module, 'checkout-only'));

        # If checkout-only not set for l10n, use kde-languages.
        if ($module eq 'l10n' and not scalar @options)
        {
            push @options, split(' ', get_option('global', 'kde-languages'));
            push @options, 'scripts';
        }

        # Make sure each submodule of l10n has their own options hash.
        if ($module eq 'l10n')
        {
            for my $lang (@options)
            {
                clone_options('l10n', "l10n/$lang");
            }
        }

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

        if ($result)
        {
            error "Error updating r[$module], removing from list of packages to build.";
            dont_build ($module);
        }

        print "\n";
    }

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

# 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' or not is_qt_copy_qt3())
    {
        # If we're in this path and module is qt-copy, it is at least qt-copy 4.

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

        # Don't allow prefix option to override qtdir setting for safety.
        $prefix = get_option($module, 'qtdir') if $module eq 'qt-copy';

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

        if ($module ne 'qt-copy')
        {
            push @commands, "CXXFLAGS=$cxxflags" if $cxxflags;
            push @commands, "--prefix=$prefix";
        }
        else # Qt 4.
        {
            # 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')
    {
        # Qt 3.
        my $qtdir = get_fullpath('qt-copy', 'build');

        # Qt 4+.
        $qtdir = get_fullpath('qt-copy', 'source') unless is_qt_copy_qt3();

        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";

    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";
            }
            elsif (checkout_module_path('kde-common', 'admin') != 0)
            {
                return 0;
            }
        }
    }

    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.
    if ($uses_cmake)
    {
        warning " b[y[*] CMake does not support the inst-apps option!" if $instapps;
        return 0; # 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\//)
    {
        my ($lang) = ($module =~ /^l10n\/(.*)$/);

        # 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.
# 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.
sub run_svn
{
    my ($module, $logfilename, $arg_ref) = @_;
    my %hash_count;
    my $result;
    my $force_refresh = 0;
    my $conflict = 0;
    my $logdir = get_log_dir($module);

    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.
    $result = log_command($module, $logfilename, $arg_ref);

    # There will be no result if we're pretending, so don't even
    # bother.
    return 0 if pretending;

    $logfilename = "$logdir/$logfilename.log";

    # We need to open the file and try to determine what the Subversion process
    # did.
    open SVN_LOG, "<$logfilename";
    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 updates and patches together.
        $hash_count{'updated'}++    if /^U[ A-Z]/;
        $hash_count{'updated'}++    if /^P[ A-Z]/;
        $hash_count{'deleted'}++    if /^D[ A-Z]/;
        $hash_count{'added'}++      if /^A[ A-Z]/;
        $hash_count{'removed'}++    if /^R[ A-Z]/;
        $hash_count{'merged'}++     if /^G[ A-Z]/;
        $hash_count{'modified'}++   if /^M[ A-Z]/;
        $hash_count{'conflicted'}++ if /^C[ 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;

    my %endings = (
        'updated'     => 'files were updated',
        '1updated'    => 'file was updated',
        'added'       => 'files were added',
        '1added'      => 'file was added',
        'removed'     => 'files were removed',
        '1removed'    => 'file was removed',
        'modified'    => 'files were modified',
        '1modified'   => 'file was modified',
        'conflicted'  => 'files had conflicts',
        '1conflicted' => 'file had conflicts',
        'deleted'     => 'files were deleted',
        '1deleted'    => 'file was deleted',
        'merged'      => 'files had changes merged',
        '1merged'     => 'file had changes merged',
    );

    my ($key, $value);
    while (($key, $value) = each %hash_count)
    {
        next unless $value > 0;
        my $ending_key = $value > 1 ? $key : ('1' . $key);
        my $ending = $endings{$ending_key};
        info "\t$value $ending.";
    }

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

        return $result;
    }

    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 $result;
}

# 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")
    {
        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.
    # Also, CMake requires srcdir != builddir so don't do this for CMake.
    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;

    if (not $uses_cmake and ($do_makeconf or 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");

    $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 $kdenonbeta = get_fullpath('kdenonbeta', 'source');
        $path = "$kdenonbeta/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);

    # Qt has several defines of its own.  Special case qt-copy for this
    # reason.
    setenv ("YACC", 'byacc -d') if ($module eq "qt-copy");

    # 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 $cur_module_num = shift;
    my $total_module_num = shift;
    my $apidox = 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);

    # This warning doesn't apply to Qt 4.
    if($module eq 'qt-copy' and $builddir ne get_option('global', 'qtdir')
        and is_qt_copy_qt3())
    {
        my $qtpath = $builddir;
        $qtpath =~ s/$ENV{HOME}/~/;
        warning <<EOF;

b[y[!!] You're building qt-copy, but QTDIR isn't set to use qt-copy!
b[y[!!] Please set your qtdir variable in the global section of your
b[y[!!] $rcfile to g[$qtpath]

EOF
    }

    my $start_time = time;
    while (not defined $package_opts{$module}->{'#was-rebuilt'})
    {
        note "Building g[$module] ($cur_module_num/$total_module_num)";
        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 = @_;

    # Only filter if 'l10n' is actually present in list.
    if (grep ($_ eq 'l10n', @modules))
    {
        @modules = grep($_ ne 'l10n', @modules); # Remove all instances of l10n

        # Prefer checkout-only (will be set from command line).
        my $subdirs = get_option('l10n', 'checkout-only');
        $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 $build_ref = shift;
    my $kdesvn = get_kdesvn_dir();
    my $module;
    my @modules = grep (!/^(KDE\/)?kde-common$/, @{$build_ref});
    my $result;
    my $outfile = get_output_file ();

    # 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  >>>";

    # Save the environment to keep module's env changes from affecting other
    # modules.
    my %env_backup = %ENV;

    if (pretending)
    {
        $outfile = undef;
    }
    else
    {
        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 ($module = shift @modules)
    {
        my $start_time = time;

        if (build_module ($module, $i, $num_modules))
        {
            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
            }
        }

        print "\n";
        %ENV = %env_backup;
        $i++;
    }

    # 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
        open BUILT_LIST, ">$kdesvn/successfully-built";
        foreach $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'}};
}

# 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} = { 'set-env' => { } };
    }
}

# 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");
    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_qt4_needs_make_install
{
    my $qtsource = get_fullpath('qt-copy', 'source');
    my $prefix = get_option('global', 'qtdir');
    return 0 if $qtsource 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 is_qt_copy_qt3()) or
            ($module eq 'qt-copy' and !qt_copy_qt4_needs_make_install())
           )
        {
            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))
            {
                $modulename .= "/" . shift @dirs;
            }

            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 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.

# 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', '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);

    # This handles use-stable-kde, and kde4-snapshot.  Basically we setup default
    # module branches for any branches not already set by the user.
    setup_module_branches(); # Default to KDE 3.5, snapshot, etc.

    # 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
        $result = handle_updates (\@update_list);
        $result = handle_build (\@build_list) || $result;
    }
    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:
