#!/usr/bin/perl -w
#
# File:		y2makeall
# Package:	devtools
# Summary:	Build all YaST2 sources from CVS or Subversion
# Authors:	Stefan Hundhammer <sh@suse.de>
#
# $Id: y2makeall 28792 2006-03-09 17:36:11Z sh $
#
# Call this from your toplevel yast2/source CVS or Subversion working directory.

use strict;
use English;
use Getopt::Long;
use File::Basename;
use IPC::Open2;
use Data::Dumper;
#
# Configuration variables.
#
# Users can override any of these in this file (Perl syntax!):

my $user_config_file = <~/.yast2/y2makeallrc>;


# Directories to exclude from building - unmaintained packages, packages that
# are not yet ready to be built, ...
#
# You may want to append to this list in your personal config file:
#
#	push @exclude_list, ( "dontwantthis", "dontwantthat" );

our @exclude_list =
    (
     "CVS",
     ".svn",
     "certify",
     "debugger",
     "ipsec",
     "packagemanager",
     "packagemanager-test"
     );

if ( $ENV{'Y2MAKEALL_EXCLUDE'} )
{
    push @exclude_list, split(/ /, $ENV{'Y2MAKEALL_EXCLUDE'});
    print Dumper(@exclude_list);
}

# Personal favourites - those directories will be processed first (in this order).
# This is what most YCP package maintainers will want to change in their personal
# ~/.yast2/y2makeallrc file to get their personal packages processed first.
# Remember to omit the "my" in your personal config file!
#
# All other directories (except of course those from the exclude list) will be
# processed automatically after those.

our @ycp_favourites =
    (
     "yast2",
     "installation",
     "packager"
     );

if ( $ENV{'Y2MAKEALL_FAVOURITES'} )
{
   print "favoritos";
   @ycp_favourites = split(/ /, $ENV{'Y2MAKEALL_FAVOURITES'});
   Dumper(@ycp_favourites);

}

# Dependencies (from all other YaST2 packages) that are deliberately ignored.
# This is necessary to break up dependency cycles which otherwise would make
# bootstrapping impossible.
#
# Yes, this is a mess. But SuSE AutoBuild does it the same way.
# There is no reasonable way to avoid this.   :-(
my %ignored_requires =
    (
     "yast2-installation"	=> 1,
     "yast2-nis-server"		=> 1,
     "yast2-gtk"		=> 1,
     "yast2-inetd"		=> 1,
     "yast2_theme"		=> 1
     );


my $work_root		= $ENV{'PWD'};
my $build_root  	= $work_root . "/build";
my $make_log		= $work_root . "/make.log";
my $failed_log		= $work_root . "/failed-packages.log";
my $cmake_build_dir	= "build"; # relative to project directory


#
# Other global variables.
#

my $disable_make_makefile_cvs			= 0;
my $favourites_only				= 0;
my $dry_run					= 0;
my $prefix					= "/usr";
my $enable_print_summary			= 1;
my $enable_make_zypp				= 0;
my $libzypp_source				= "";
my $libzypp_prefix				= "/usr";
my $icecream_prefix				= "/opt/icecream";
my $jobs					= 8;
my $zypp_defines				= "";

# Verbosity values

use constant
{
    LOG_ALWAYS		=> 999,
    LOG_MILESTONE	=> 3,
    LOG_PROGRESS	=> 2,
    LOG_INFO		=> 1,
    LOG_DEBUG		=> 0
};

my $tty_verbosity		= LOG_PROGRESS;
my $log_verbosity		= LOG_INFO;

my $make_target			= "";
my $sudo_cmd			= "/usr/bin/sudo";
my $install_cmd			= "/usr/bin/install";


# ---- END configuration variables ----------------------------------------


my $prog_name = basename( $0 );
my %exclude_dirs;
my %rpm_names;
my %rpm_dirs;
my %relevant_rpms;
my %pkg_provides;
my %pkg_requires;
my %pkg_buildrequires;
my %provided_by;
my %base_rpms;
my @build_order;
my @failed_dirs;
my $successful_count = 0;


#
# Forward declarations.
#

sub main();


# Call the main function and exit.
# DO NOT enter any other code outside a sub -
# any variables would otherwise be global.


main();
exit 0;


#-----------------------------------------------------------------------------


sub main()
{
    my $show_help		= 0;
    my $verbose			= 0;
    my $debug			= 0;
    my $silent			= 0;
    my $no_sudo			= 0;
    my $dump_build_order	= 0;
    my $dump_dependencies	= 0;
    my $dump_builddependencies	= 0;
    my $ignore_libzypp		= 0;
    my $build_libzypp		= 0;
    my $enable_icecream		= 0;

    GetOptions( "favourites-only"	=> \$favourites_only,
		"fast"			=> \$disable_make_makefile_cvs,
		"prefix=s"		=> \$prefix,
		"no-sudo"		=> \$no_sudo,
		"dump-build-order"	=> \$dump_build_order,
		"dump-dependencies"	=> \$dump_dependencies,
		"dump-builddependencies"=> \$dump_builddependencies,
		"dry-run"		=> \$dry_run,
		"ignore-libzypp"	=> \$ignore_libzypp,
		"build-libzypp"		=> \$build_libzypp,
		"libzypp-source=s"	=> \$libzypp_source,
		"libzypp-prefix=s"	=> \$libzypp_prefix,
		"icecream"		=> \$enable_icecream,
		"icecream-prefix=s"	=> \$icecream_prefix,
		"jobs=i"		=> \$jobs,
		"rcfile=s"		=> \$user_config_file,
		"verbose"		=> \$verbose,
		"debug"			=> \$debug,
		"silent"		=> \$silent,
		"help"			=> \$show_help
		);

    usage() if $show_help;

    if ( $verbose)
    {
	$tty_verbosity	= LOG_INFO;
	$log_verbosity	= LOG_DEBUG;
    }

    if ( $debug )
    {
	$tty_verbosity	= LOG_DEBUG;
	$log_verbosity	= LOG_DEBUG;
    }

    if ( $silent )
    {
	$tty_verbosity	= LOG_MILESTONE;
	$log_verbosity	= LOG_PROGRESS;
    }

    $sudo_cmd = "" if $no_sudo;

    #
    # Check if we are in the right directory
    #

    die "FATAL: Call \"$prog_name\" from your YaST2 CVS or Subversion working directory! (yast2/source)\n"
	unless -f "devtools/RPMNAME";

    #
    # Check if correct zypp directory was specified
    #

    if ( $build_libzypp ) {
	die "FATAL: wrong zypp source dir specified!\n"
	    unless -f "$libzypp_source/libzypp.pc.cmake";
    }

    #
    # Set up logs
    #

    open( LOG,        ">" . $make_log   ) or warn "Couldn't open $make_log";
    open( FAILED_LOG, ">" . $failed_log ) or warn "Couldn't open $failed_log";
    $OUTPUT_AUTOFLUSH = 1;

    #
    # Build libzypp if necessary and
    # make sure we have exactly one version of libzypp installed
    #

    make_libzypp_build() if $build_libzypp;
    check_libzypp() unless $ignore_libzypp;

    #
    # disable icecream_prefix if we don't need it
    #

    $icecream_prefix = "" unless $enable_icecream;

    #
    # All remaining command line parameters go to "make".
    #

    $make_target = join( " ", @ARGV );


    #
    # Read user configuration from ~/.yast2/y2makeallrc (if present)
    #
    # NOTE: This must be done before anything else happens, in particular,
    # before the log file is opened - the user might have chosen to change it in
    # his config file!
    #

    eval { require $user_config_file } if ( -f $user_config_file );


    #
    # Make a hash from the exclude list
    #

    @exclude_dirs{ @exclude_list } = (); # Black magic - see Perl cook book chapter 4.7


    #
    # Calculate dependencies and build order
    #


    read_rpm_names();
    calc_build_order();

    if ( $dump_dependencies )
    {
	dump_dependencies();
	exit;
    }

    if ( $dump_builddependencies )
    {
	dump_builddependencies();
	exit;
    }

    if ( $dump_build_order )
    {
	dump_build_order();
	exit;
    }



    #
    # Build
    #

    make_in_build_order();


    #
    # Clean up
    #

    print_summary()		if $enable_print_summary;
    close( LOG );
    close( FAILED_LOG );

    unlink( $failed_log ) if ( scalar( @failed_dirs ) < 1 );
}


#-----------------------------------------------------------------------------

# Read all RPM names from all $work_root/*/RPMNAME files and store them in
# the global %rpm_names hash

sub read_rpm_names()
{
    chdir( $work_root );
    undef %rpm_names;
    my $rpmname_file;

    foreach $rpmname_file ( <*/RPMNAME> )
    {
	if ( open( RPMNAME, $rpmname_file ) )
	{
	    my $dir = dirname( $rpmname_file );
	    my $line;

	    while ( $line = <RPMNAME> )
	    {
		chomp( $line );
		$line =~ s/^\s+//;
		$line =~ s/\s$//;

		if ( $line )
		{
		    if ( defined( $rpm_names{ $dir } ) )
		    {
			log_warning( "Multiple RPM names for $dir: $rpm_names{ $dir }, $line" );
		    }
		    else
		    {
			$rpm_names{ $dir } = $line;
			$rpm_dirs{ $line } = $dir;
		    }
		}
	    }

	    close( RPMNAME);
	}
	else
	{
	    log_warning( "Can't open $rpmname_file" );
	}
    }

    %relevant_rpms = reverse( %rpm_names );
}


#-----------------------------------------------------------------------------

# Read dependencies and calculate the build order (in global @build_order).

sub calc_build_order()
{
    read_all_dependencies();
    expand_provides();
    %pkg_buildrequires = %pkg_requires;
    kick_irrelevant_requires();
    tsort_dependencies();
}


#-----------------------------------------------------------------------------

# Read all .spec.in files below $work_root and store the "Requires" and
# "BuildRequires" in the global %pkg_requires hash

sub read_all_dependencies()
{
    chdir( $work_root );
    my $spec_in_file;

    foreach $spec_in_file ( <*/*.spec.in> )
    {
	my $rpm = $rpm_names{ dirname( $spec_in_file ) };
	log_error( "No dir name for $spec_in_file" ) unless defined $rpm;

	my ( $this_pkg_requires, $this_pkg_provides ) = read_dependencies( $rpm, $spec_in_file );
	$pkg_requires{ $rpm } = $this_pkg_requires;
	$pkg_provides{ $rpm } = $this_pkg_provides;
    }
}


#-----------------------------------------------------------------------------

# Read depencencies ("Requires" and "BuildRequires" from one .spec.in file.
# Filter out only relevant packages.
#
# Parameters:
#	$this_pkg		RPM name of this package
#	$spec_in_file_name	file name to read
#
# Return value:
#	Hash reference with all relevant depencencies (keys are RPM names, values are "1")

sub read_dependencies($)
{
    my( $this_pkg, $spec_in_file_name ) = @_;
    my %this_pkg_requires;
    my %this_pkg_provides;

    log_debug( "Reading dependencies for $this_pkg" );

    if ( ! open( SPEC_IN, $spec_in_file_name ) )
    {
	log_error( "Can't open $spec_in_file_name" );
	return ( \%this_pkg_requires, \%this_pkg_provides );
    }

    my $line;

    while ( $line = <SPEC_IN> )
    {
	chomp( $line );
	$line =~ s/#.$//;	# ignore comments

	if ( $line =~ /^\s*(Requires|BuildRequires)\s*:/ )
	{
	    $line =~ s/^\s*(Requires|BuildRequires)\s*:\s*//;
	    my $rpm;

	    foreach $rpm ( split( '\s+', $line ) )
	    {
		if ( defined( $ignored_requires{ $rpm } ) )
		{
		    # Strictly speaking, this is a mess: All dependencies should be correct.
		    # There should be no cycles in build dependencies.
		    # But in real life, they exist. So some of them need to be suppressed.
		    #
		    # SuSE AutoBuild, too has exception lists for each distribution
		    # to ignore some of them. Otherwise bootstrapping is simply not possible.

		    log_debug( "\t# IGNORING: $this_pkg requires $rpm" );
		}
		elsif ( $rpm =~ /[a-zA-Z_+-]+[a-zA-Z0-9_+-]*/ )
		{
		    log_debug( "\t$this_pkg requires $rpm" );
		    $this_pkg_requires{ $rpm } = 1;
		}
		else
		{
		    log_debug( "\tIgnoring cruft \"$rpm\"" );
		}
	    }
	}
	elsif ( $line =~ /^\s*(Provides)\s*:/ )
	{
	    $line =~ s/^\s*(Provides)\s*:\s*//;
	    my $prov;

	    foreach $prov ( split( '\s+', $line ) )
	    {
		if ( $prov =~ /^[a-zA-Z_+-]+[a-zA-Z0-9_+-]*$/ )
		{
		    log_debug( "\t$this_pkg provides $prov" );
		    $this_pkg_provides{ $prov } = 1;
		}
		else
		{
		    log_debug( "\t# IGNORING: $this_pkg provides $prov" );
		}
	    }
	}
    }

    close( SPEC_IN );

    return ( \%this_pkg_requires, \%this_pkg_provides );
}


#-----------------------------------------------------------------------------

# Do a topological sort (using the external tsort(1) program) to calculate the
# correct build order.

sub tsort_dependencies()
{
    my $pid = open2( *TSORT_OUT, *TSORT_IN, 'tsort' );
    my $rpm;

    #
    # Pipe all "requires" to the tsort(1) program (part of coreutils.rpm)
    #

    foreach $rpm ( sort( keys( %pkg_requires ) ) )
    {
	my $req = $pkg_requires{ $rpm };
	my $this_pkg_requires;

	foreach $this_pkg_requires ( sort( keys( %$req ) ) )
	{
	    print TSORT_IN "$rpm $this_pkg_requires\n";
	}
    }

    close( TSORT_IN );


    #
    # Read the output of the tsort(1) program
    #

    my $line;

    while ( $line = <TSORT_OUT> )
    {
	chomp( $line );
	$line =~ s/^\s+//;
	$line =~ s/\s+$//;

	push( @build_order, $line );
    }

    close( TSORT_OUT );
    @build_order = reverse( @build_order );


    #
    # Find the leaf packages to honour the user's favourites
    #

    my @base_rpms_build_order;
    my %leaf_rpms;

    foreach $rpm ( @build_order )
    {
	if ( defined( $base_rpms{ $rpm } ) )
	{
	    push( @base_rpms_build_order, $rpm )
	}
	else
	{
	    $leaf_rpms{ $rpm } = 1;
	}
    }


    #
    # Move favourites (that are leaf packages) to the front of the leaf package build order
    #

    @build_order = @base_rpms_build_order;
    my $favourite;

    foreach $favourite ( @ycp_favourites )
    {
	if ( defined( $leaf_rpms{ $favourite } ) or		 # RPM name specified in favourites
	     ( defined( $rpm_names{ $favourite } ) and
	       defined( $leaf_rpms{ $rpm_names{ $favourite } } ) ) ) # directory specified
	{
	    $favourite = $rpm_names{ $favourite } unless defined $leaf_rpms{ $favourite };
	    log_info( "Preferring favourite $favourite" );
	    push( @build_order, $favourite );
	    $leaf_rpms{ $favourite } = undef;
	}
    }

    #
    # Append what is left in alphabetical order
    #

    push( @build_order, sort( keys( %leaf_rpms ) ) ) unless $favourites_only;
}


#-----------------------------------------------------------------------------

# Output the build order

sub dump_build_order()
{
    my $rpm;
    my $leaf_package_count = 0;

    log_always( "\n### Base Packages ###\n" ) if defined( $base_rpms{ $build_order[0] } );

    foreach $rpm ( @build_order )
    {
	log_always( $rpm );

	$leaf_package_count++ if ( ! defined( $base_rpms{ $rpm } ) );
	log_always( "\n### Leaf Packages ###\n" ) if $leaf_package_count == 1;
    }
}


#-----------------------------------------------------------------------------

# Dump all depencencies to stdout.

sub dump_dependencies()
{
    my $rpm;

    foreach $rpm ( sort( keys( %pkg_requires ) ) )
    {
	my $req = $pkg_requires{ $rpm };
	my $this_pkg_requires;

	foreach $this_pkg_requires ( sort( keys( %$req ) ) )
	{
	    log_always( "$rpm", $this_pkg_requires );
	}
    }
}

#-----------------------------------------------------------------------------

# Dump all build depencencies to stdout.

sub dump_builddependencies()
{
    my $rpm;

    foreach $rpm ( sort( keys( %pkg_buildrequires ) ) )
    {
	my $req = $pkg_buildrequires{ $rpm };
	my $this_pkg_requires;

	foreach $this_pkg_requires ( sort( keys( %$req ) ) )
	{
	    log_always( "$rpm", $this_pkg_requires );
	}
    }
}


#-----------------------------------------------------------------------------

# Expand all RPM "provides" from the global %provides hash in all dependencies
# in the global %requires hash: Replace any dependencies that are satisfied
# with any capability in %provides with the package that provides that
# capability.
#
# Example:
#
#    yast2         requires yast2-ui
#    yast2-qt      provides yast2-ui
#    yast2-ncurses provides yast2-ui
#
# will be changed to
#
#    yast2 requires yast2-qt
#    yast2 requires yast2-ncurses
#    (deleting depencendy "yast2 requires yast2-ui")
#
# While this is not really correct in the RPM world, it will do nicely for
# calculating the build order, which is what this script needs to do.

sub expand_provides()
{
    find_providers();	# fill global %provided_by hash

    my $pkg;

    foreach $pkg ( keys %pkg_requires )
    {
	my $old_requires = $pkg_requires{ $pkg };
	my %new_requires;
	my $req;

	foreach $req ( keys %$old_requires )
	{
	    if ( defined( $provided_by{ $req } ) )
	    {
		my $providers = $provided_by{ $req };
		log_debug( "Expanding requires: $pkg requires $req -> $pkg requires @$providers" );
		my $prov;

		foreach $prov ( @$providers )
		{
		    $new_requires{ $prov } = 1;
		}
	    }
	    else
	    {
		$new_requires{ $req } = 1;
	    }
	}

	$pkg_requires{ $pkg } = \%new_requires;
    }
}


#-----------------------------------------------------------------------------

# Find the providers (the packages that provide a certain capability):
# Go through all package provides in the global %pkg_provides hash and
# fill the global %provided_by hash with it.

sub find_providers()
{
    my $pkg;

    foreach $pkg ( keys %pkg_provides )
    {
	my $this_pkg_provides = $pkg_provides{ $pkg };
	my $prov;

	foreach $prov ( keys %$this_pkg_provides )
	{
	    my $providers;
	    $providers = $provided_by{ $prov } if defined $provided_by{ $prov };
	    push( @$providers, $pkg );
	    $provided_by{ $prov } = $providers;
	}
    }


    if ( $log_verbosity <= LOG_DEBUG or
	 $tty_verbosity <= LOG_DEBUG   )
    {
	my $prov;

	foreach $prov ( keys %provided_by )
	{
	    my $providers = $provided_by{ $prov };
	    log_debug( "$prov\tis provided by", @$providers );
	}
    }
}


#-----------------------------------------------------------------------------

# Remove all "requires" dependencies from the global %pkg_requires hash that
# don't refer to anything in the global %relevant_rpms hash: We don't need to
# bother about dependencies to gcc and glibc for the purpose of finding out the
# build order for YaST2.
#
# Note: It's not as trivial as it seems - not all YaST2 packages are named
# yast2-xxx. This is why the content of all RPMNAME files was stored in earlier
# phases.
#
# As a side effect, this function also fills the global hash %base_rpms with
# the names of all packages that have dependent packages (i.e. some other
# package depends on them).

sub kick_irrelevant_requires()
{
    # log_debug( "Relevant RPMs: ", sort( keys( %relevant_rpms ) ) );
    my $pkg;

    foreach $pkg ( keys %pkg_requires )
    {
	my $old_requires = $pkg_requires{ $pkg };
	my %new_requires;
	my %new_buildrequires;
	my $req;

	foreach $req ( keys %$old_requires )
	{
	    if ( defined $relevant_rpms{ $req } )
	    {
		$new_requires{ $req } = 1;
		$base_rpms{ $req } = 1;
	    }
	    else
	    {
		if ( $req =~ /^yast2-/ )
		{
		    log_info( "Kicking bogus dependency \"$pkg requires $req\"" );
		}
		else
		{
		    log_debug( "Kicking irrelevant dependency \"$pkg requires $req\"" );
		    $new_buildrequires{ $req } = 1;
		}
	    }
	}

	$pkg_requires{ $pkg } = \%new_requires;
	$pkg_buildrequires{ $pkg } = \%new_buildrequires;
    }
}


#-----------------------------------------------------------------------------

# Output base packages (those that have any dependent packages)

sub dump_base_packages()
{
    my $rpm;

    log_always( "\nBase packages:\n" );

    foreach $rpm ( sort( keys( %base_rpms ) ) )
    {
	my $dir = $rpm_dirs{ $rpm };

	if ( $rpm =~ /(yast2-)?$dir/ )
	{
	    log_always( "\t$rpm" );
	}
	else
	{
	    log_always( "\t$rpm\t(directory \"$dir\")" );
	}
    }

    log_always( "" );
}


#-----------------------------------------------------------------------------

# Output leaf packages (those that don't have any dependent packages)

sub dump_leaf_packages()
{
    my $rpm;

    log_always( "\nLeaf packages:\n" );

    foreach $rpm ( sort( keys( %relevant_rpms ) ) )
    {
	if ( ! defined $base_rpms{ $rpm } )
	{
	    my $dir = $rpm_dirs{ $rpm };

	    if ( $rpm =~ /(yast2-)?$dir/ )
	    {
		log_always( "\t$rpm" );
	    }
	    else
	    {
		log_always( "\t$rpm\t(directory \"$dir\")" );
	    }
	}
    }

    log_always( "" );
}


#-----------------------------------------------------------------------------

# Call "system" and then exit if we got interrupted by a signal

sub system_int($)
{
    my $cmd = shift;
    my $ret = system( $cmd );
    my $sig = $ret & 127;

    if ( $sig == 2 || $sig == 15 )
    {
	die "Got SIGINT or SIGTERM, exiting.\n";
    }
    return $ret;
}


#-----------------------------------------------------------------------------


# Call a command with "sudo"

sub sudo_system_int($)
{
    my $cmd = shift;

    return system_int( $sudo_cmd . " " . $cmd );
}


#-----------------------------------------------------------------------------


# Build in build order

sub make_in_build_order()
{
    #
    # Set up hash with packages not to process
    #

    my %dont_process = %exclude_dirs;

    log_debug( "Excluding packages " . join( " ", sort ( keys( %dont_process ) ) ) );


    #
    # Process packages
    #

    my $pkg;

    foreach $pkg ( @build_order )
    {
	my $dir = $work_root . "/" . $rpm_dirs{ $pkg };

	if ( -d $dir )
	{
	    if ( ! exists( $dont_process{ $rpm_dirs{ $pkg } } ) )
	    {
		# log_progress( "Building $pkg" );
		make( $dir );
	    }
	    else
	    {
		log_progress( "Excluding $pkg" );
	    }
	}
	else
	{
	    log_error( "No such directory: $dir (for package $pkg)" );
	}
    }
}


#-----------------------------------------------------------------------------

# Make one package.
#
# Parameters:
#	$pkg	package to build (from $work)

sub make()
{
    my( $pkg_dir ) = @_;
    my $error = 0;

    chdir $work_root;
    chdir $pkg_dir;


    # Check if this is a CMake-based project.

    my $is_cmake_project = 0;
    $is_cmake_project = 1 if -f "CMakeLists.txt" && ! ( -f "configure.in.in" || -f "configure.in" );

    #
    # Display progress
    #

    log_progress( "Building", basename( $pkg_dir ),
		  $is_cmake_project ? "(cmake based)" : "" );


    #
    # Prepare make environment
    #

    my $path = "$prefix/bin:\$PATH";
    $path = "$icecream_prefix/bin:$path" if $icecream_prefix;

    my $ld_library_path =
	"$libzypp_prefix/lib64"
	. ":$libzypp_prefix/lib"
	;

    my $pkg_config_path =
	"$libzypp_prefix/lib64/pkgconfig"
	. ":$libzypp_prefix/lib/pkgconfig"
	. ":$prefix/lib64/pkgconfig"
	. ":$prefix/lib/pkgconfig"
	. ":$prefix/share/pkgconfig"
	;

    my $perllib =
	"$prefix/lib/perl5/5.8.8/x86_64-linux-thread-multi"
	. ":$prefix/lib/perl5/5.8.8"
	. ":$prefix/lib/perl5/vendor_perl/5.8.8/x86_64-linux-thread-multi"
	. ":$prefix/lib/perl5/vendor_perl/5.8.8"
	. ":$prefix/lib/perl5/vendor_perl"
	;

    my $make_env =
	"PATH=\"$path\""
	. " YCPC_Y2DIR=$prefix/share/YaST2"
	. " PREFIX=$prefix"
	. " PKG_CONFIG_PATH=\"$pkg_config_path\$PKG_CONFIG_PATH\""
	. " YCPC_LD_LIBRARY_PATH=\"$ld_library_path\""
	. " PERLLIB=\"$perllib\""
	;


    # Check if "make -f Makefile.cvs" must be performed.
    # Even if command line option "-f" (fast mode - sets $disable_make_makefile_cvs)
    # is given, it still needs to be done if there is no toplevel Makefile.

    my $do_make_makefile_cvs = 1;
    $do_make_makefile_cvs = 0 if $disable_make_makefile_cvs;
    my $toplevel_makefile = $is_cmake_project ? "$cmake_build_dir/Makefile" : "Makefile";
    $do_make_makefile_cvs = 1 unless -f $toplevel_makefile;

    #
    # make -f Makefile.cvs
    #

    if ( $do_make_makefile_cvs )
    {
	log_info( "\trm -rf autom4te.cache" );
	system_int( "rm -rf autom4te.cache >>$make_log 2>&1" ) unless $dry_run;

	log_progress( "\tmake -f Makefile.cvs" );
	system_int( "$make_env make -e -f Makefile.cvs >>$make_log 2>&1" ) unless $dry_run;

	if ( $CHILD_ERROR )
	{
	    log_error( "'make -f Makefile.cvs' failed for $pkg_dir" );
	    $error = 1;
	}
    }


    #
    # make
    #

    if ( $is_cmake_project )
    {
	chdir( $cmake_build_dir );
	my $dir = basename( $pkg_dir );
	log_progress( "\tcd $dir/$cmake_build_dir" );
	# system_int( "pwd" );
    }

    if ( ! $error )
    {
	log_progress( "\tmake $make_target" );
	system_int( "$make_env make -e -j$jobs $make_target >>$make_log 2>&1" ) unless $dry_run;

	if ( $CHILD_ERROR )
	{
	    log_error( "'make -j$jobs $make_target' failed for $pkg_dir" );
	    $error = 1;
	}
    }


    #
    # sudo make install
    #

    if ( $make_target eq "" && ! $error )
    {
	log_progress( "\t$sudo_cmd make install" );
	sudo_system_int( "make install >>$make_log 2>&1" ) unless $dry_run;

	if ( $CHILD_ERROR )
	{
	    log_error( "'$sudo_cmd make install' failed for $pkg_dir" );
	    $error = 1;
	}
    }


    #
    # Summary
    #

    if ( $error )
    {
	my $dir = basename( $pkg_dir );
	push( @failed_dirs, $dir );
	my $rpm = $rpm_names{ $dir };
	my $msg = "$dir";
	$msg .= "\t\t\t(base package)" if defined( $base_rpms{ $rpm } );
	print FAILED_LOG "$msg\n";
    }
    else
    {
	log_progress( "\tOK" );
	$successful_count++;
    }

    chdir $work_root;
}


#-----------------------------------------------------------------------------


# Build libzypp

sub make_libzypp_build()
{
    # zypp cmake defines

    my $zypp_defines = "-DCMAKE_INSTALL_PREFIX=$libzypp_prefix";

    non_y2_cmake( $libzypp_source, $zypp_defines );
}


#-----------------------------------------------------------------------------

# Make a non-YaST2 project that is based on cmake.
#
# Parameters:
#	$dir		source dir
#	$defines	cmake build defines

sub non_y2_cmake()
{
    my( $pkg_dir, $defines ) = @_;
    my $error = 0;
    my $pkg = basename( $pkg_dir );

    chdir $work_root;

    log_progress( "Building", $pkg );
    log_progress( "\tcreate temp. build dir" );

    system_int( "mkdir -p $build_root/$pkg >>$make_log 2>&1" );

    if ( $CHILD_ERROR )
    {
	log_error( "'mkdir -p $build_root/$pkg' failed for $pkg: $!" );
	$error = 1;
    }

    #
    # cmake in build dir
    #

    if ( ! $error )
    {
	chdir "$build_root/$pkg";

	log_progress( "\tcmake build in $pkg_dir" );

	system_int( "cmake $defines $pkg_dir >>$make_log 2>&1" );

	if ( $CHILD_ERROR )
	{
	    log_error( "'cmake $defines $pkg_dir' failed for $pkg" );
	    $error = 1;
	}
    }

    #
    # make
    #

    if ( ! $error )
    {
	log_progress ( "\tmake $pkg" );
	system_int( "make -C $build_root/$pkg -j$jobs $make_target >>$make_log 2>&1" );

	if ( $CHILD_ERROR )
	{
	    log_error( "'make -C $build_root/$pkg -j$jobs $make_target' failed for $pkg" );
	    $error = 1;
	}
    }

    #
    # sudo make install
    #

    if ( $make_target eq "" && ! $error )
    {
	log_progress( "\t$sudo_cmd make -C $build_root/$pkg install" );
	sudo_system_int( "make -C $build_root/$pkg install >>$make_log 2>&1" );

	if ( $CHILD_ERROR )
	{
	    log_error( "'$sudo_cmd make -C $build_root/$pkg install' failed for $pkg" );
	    $error = 1;
	}
    }

    #
    # Summary
    #

    if ( $error )
    {
	push( @failed_dirs, $pkg );
	print FAILED_LOG "$pkg\n";
    }
    else
    {
	log_progress( "\tOK" );
	$successful_count++;
    }

    chdir $work_root;
}


#-----------------------------------------------------------------------------


sub print_summary()
{
    my $failed_count = scalar( @failed_dirs );
    return if $successful_count + $failed_count < 1;	# didn't really build anything

    log_milestone( "\n$prog_name $make_target summary:\n" );
    log_milestone( "    Success in $successful_count directories" );

    if ( $failed_count > 0 )
    {
	log_milestone( "    Failure in " . $failed_count . " directories:\n" );
	my $failed_dir;

	foreach $failed_dir ( @failed_dirs )
	{
	    my $rpm = $rpm_names{ $failed_dir };
	    my $msg = "\t$failed_dir";
	    $msg .= "\t\t\t(base package)" if defined( $base_rpms{ $rpm } );

	    log_milestone( $msg );
	}
    }
    else
    {
	log_milestone( "    No failures" );
    }
}


#-----------------------------------------------------------------------------

# (Recursively) find all .ycp files in directory tree $dir that match $pattern,
# but not $exclude_regexp.
#
# Parameters:
#	Directory to start searching
#	File pattern ("*.ycp", "[A-Z]*.ycp")
#	Exclude regexp ( "(testsuite|examples)" )
#
# Returns:
#	Array of .ycp files

sub find_files()
{
    my ( $dir, $pattern, $exclude_regexp ) = @_;
    my $cmd = "find -H $dir -name \"$pattern\" -print | egrep -v \"$exclude_regexp\"";

    log_debug( "$cmd" );
    my @files = split( '\n', `$cmd`);

    return @files;
}


#-----------------------------------------------------------------------------

# Check if exactly one version of libzypp is installed.
#
# Multiple versions of libzypp ususally result in very subtle problems or in
# crashing build components like the YCP byte code compiler: If one YaST2
# binary package is built with libzypp.so.42 and another with libzypp.so.43,
# they will be binary incompatible. This typically affects yast2-pkg-bindings,
# yast2-perl-bindings, yast2-qt, yast2-ncurses.
#
# Result:
#	Terminate program upon error

sub check_libzypp()
{
    my @libs = <$libzypp_prefix/lib/libzypp.so* $libzypp_prefix/lib64/libzypp.so.*>;
    my @libzypp;
    my $libzypp_count = 0;
    my $lib;

    foreach $lib ( @libs )
    {
	if ( ! -l $lib )	# disregard symlinks
	{
	    push @libzypp, $lib;
	    $libzypp_count++;
	}
    }

    if ( $libzypp_count > 1 )
    {

	die <<EOF

FATAL: Found $libzypp_count versions of libzypp:

    @libzypp

If you are absolutely sure that this is OK, use
--ignore-libzypp to override.

EOF
    }
    elsif ( $libzypp_count == 0 )
    {
	die <<EOF

FATAL: No libzypp found below $libzypp_prefix/lib or $libzypp_prefix/lib64

If you are absolutely sure that this is OK, use
--ignore-libzypp to override.

EOF
    }
}


#-----------------------------------------------------------------------------

# Logging functions - messages go to stdout and to the log file (make.log)
# depending on the respective verbosities of each.

sub log_always()
{
    log_msg( LOG_ALWAYS, @_ );
}

sub log_milestone()
{
    log_msg( LOG_MILESTONE, @_ );
}

sub log_progress()
{
    log_msg( LOG_PROGRESS, @_ );
}

sub log_info()
{
    log_msg( LOG_INFO, @_ );
}

sub log_debug()
{
    log_msg( LOG_DEBUG, @_ );
}


#-----------------------------------------------------------------------------

# Log errors to stderr and to the log file - unconditionally.

sub log_error()
{
    log_stderr( ( "ERROR: ", @_ ) );
}

#-----------------------------------------------------------------------------

# Log warnings to stderr and to the log file - unconditionally.

sub log_warning()
{
    log_stderr( ( "WARNING: ", @_ ) );
}

#-----------------------------------------------------------------------------

# Log a message to stdout and to the log file (make.log) depeding on the current
# log levels.
#
# Parameters:
#	(int) log level
#	(array) messages

sub log_msg()
{
    my $log_level = shift;
    my $msg = join( " ", @_ ) . "\n";

    $OUTPUT_AUTOFLUSH = 1;	# inhibit buffering

    print $msg		if $log_level >= $tty_verbosity;
    print LOG $msg	if $log_level >= $log_verbosity;
}


#-----------------------------------------------------------------------------


# (Unconditionally) log a message to stderr and to the log file (make.log)
#
# Parameters:
#	(array) messages

sub log_stderr()
{
    my $msg = join( " ", @_ ) . "\n";

    $OUTPUT_AUTOFLUSH = 1;	# inhibit buffering

    print STDERR $msg;
    print LOG    $msg;
}


#-----------------------------------------------------------------------------


# Print usage message and abort program.
#
# Parameters:
#	---

sub usage()
{
    die <<"HELP_END";

Usage: $prog_name [opt] [make-target]

Builds all YaST2 sources from CVS or Subversion.
Call this from your toplevel yast2/source CVS or SVN working directory!

	--fast			no "make -f Makefile.cvs" if it can be avoided
	--favourites-only	stop processing after favourites list is done

	--prefix <prefix>	use <prefix> instead of /usr
	-n  (--no-sudo)		do not sudo certain commands (useful with --prefix)

	--build-libzypp		build libzypp, too
	--libzypp-source <dir>	libzypp source directory, required for building libzypp
	--libzypp-prefix <prefix> use <prefix> instead of /usr

	--dump-build-order	only dump the build order to stdout and log
	--dump-dependencies	only dump package dependencies to stdout and log
	--dump-builddependencies only dump package dependencies outside of YaST to stdout and log
	--dry-run		do not build anything, only print what would be done
	--ignore-libzypp	do not complain if multiple versions of libzypp are installed

	--icecream		build on icecream
	--icecream-prefix <prefix> use <prefix> instead of /opt/icecream
	--jobs			Specifies the number of jobs to run simultaneously (default 8)

	--rcfile <rcfile> 	use alternative location for reading y2makeallrc file
	-v  (--verbose)		verbose output
	-d  (--debug)		debug output (even more verbose)
	-s  (--silent)		turn verbosity down
	-h  (--help)		(this message)

EXAMPLES:

Build everything:
	$prog_name

Do a "make pot" in all packages:
	$prog_name pot

Default values can be overridden in $user_config_file
Remember to look into $make_log for build errors!

HELP_END

}



# EOF
