#!/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::Std;
use File::Basename;
use vars qw(
	     $opt_a
	     $opt_b
	     $opt_d
	     $opt_f
	     $opt_h
	     $opt_l
	     $opt_m
	     $opt_n
	     $opt_p
	     $opt_s
	     $opt_u
	     $opt_v
	     $opt_y
	     );

#
# Configuration variables.
#
# Users can override any of these in this file (Perl syntax!):

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


# Core binary packages that need to be built in that order before any YCP code
# can be built.
my @core_binaries =
    (
     "devtools",
     "liby2util",
     # "packagemanager",
     "core",
     "pkg-bindings",
     "qt",
     "ncurses",
     "testsuite",
     "perl-bindings",
     "storage"
     );


# Mixed packages that belong to the core binaries but still have YCP code that
# should be built after modules are byte-compiled

my @core_and_ycp =
    (
     "storage"
     );

# 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",
     "openteam",
     "certify",
     "debugger"
     );

# 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",
     "xml",
     "installation",
     "storage",
     "country",
     "bootloader",
     "mouse",
     "x11",
     "packager",
     "transfer",
     "update",
     "autoinstallation",
     );



my $work_root	= $ENV{'PWD'};
my $make_log	= $work_root . "/make.log";
my $failed_log	= $work_root . "/failed-packages.log";


#
# Other global variables.
#

my $disable_make_makefile_cvs			= 0;		# -f
my $enable_make_core_binaries			= 0;		# -b
my $enable_byte_compile_modules			= 0;		# -m
my $enable_byte_compile_leftover_modules	= 0;		# -l
my $enable_make_ycp_packages			= 0;		# -y
my $enable_make_all				= 1;		# -a
my $favourites_only				= 0;		# -u
my $prefix					= "/usr";	# -p
my $enable_print_summary			= 1;

# 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			= "";
# if we don't install to usr, it is not necessary
# -n means "no sudo"
# note the trailing space
my $sudo_cmd			= "/usr/bin/sudo";
my $install_cmd			= "/usr/bin/install";

# assigned only after the prefix option could be evaluated
my $ycpc_cmd;
my $modules_dir;
my $ycp_include_dir;


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


my $prog_name = basename( $0 );
my @ycp_files;
my %ycp_modules;
my %needed_ycp_include_files;
my %included_by;
my %ycp_install_dirs;
my %exclude_dirs;
my %done_dirs;
my @failed_dirs;
my $successful_dirs = 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()
{
    # Extract command line options.
    # This will set a variable opt_? for any option,
    # e.g. opt_v if option '-v' is passed on the command line.

    getopts('hvsdfbmylaunp:');

    usage()		if $opt_h;

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

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

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


    $enable_make_all				= 0	if $opt_b or $opt_m or $opt_y or $opt_l;
    $enable_make_core_binaries			= 1	if $opt_b;
    $enable_byte_compile_modules		= 1	if $opt_m;
    $enable_byte_compile_leftover_modules	= 1	if $opt_l or $opt_m;
    $enable_make_ycp_packages			= 1	if $opt_y or $opt_u;

    $disable_make_makefile_cvs			= 1	if $opt_f;
    $favourites_only				= 1	if $opt_u;

    $sudo_cmd = ""				if $opt_n;
    $prefix = $opt_p				if $opt_p;

    $ycpc_cmd		= "$prefix/bin/ycpc";
    $modules_dir	= "$prefix/share/YaST2/modules";
    $ycp_include_dir	= "$prefix/share/YaST2/include";

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

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


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

    #
    # Make a hash from the exclude list
    #

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


    #
    # Build phases
    #

    make_core_binaries()		if $enable_make_all or $enable_make_core_binaries;
    byte_compile_modules()		if $enable_make_all or $enable_byte_compile_modules;
    byte_compile_leftover_modules()	if $enable_make_all or $enable_byte_compile_leftover_modules;
    make_ycp_packages()			if $enable_make_all or $enable_make_ycp_packages;

    #
    # Clean up
    #

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

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


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

# 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 the YaST2 core binaries - those packages that are required for
# everything else.

sub make_core_binaries()
{
    log_milestone( "\n--- Phase 1 (-b): Building core binaries..." );

    my $dir;

    foreach $dir ( @core_binaries )
    {
	make( $dir );
    }

    log_milestone( "--- Core binaries done.\n" );
}


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

# Byte-compile all YCP modules in all subdirectories.

sub byte_compile_modules()
{
    log_milestone( "\n--- Phase 2 (-m): Byte-compiling YCP modules..." );

    #
    # Find all YCP modules in CVS working directory
    #

    # Make use of YCP programming conventions: Module names start with a capital letter.
    my @module_candidates = find_files( $work_root, "[A-Z]*.ycp", "(testsuite|examples|skeleton)" );

    log_info( "Exclude list: " . join( " ", sort( keys %exclude_dirs ) ) );
    my $file;

    foreach $file ( @module_candidates )
    {
	if ( is_ycp_module( $file ) )
	{
	    my $pkg = $file;
	    $pkg =~ s:^$work_root/::;	# remove leading work root
	    $pkg =~ s:/.*$::;		# remove all path components but the first
	    my $mod = basename( $file );

	    if ( exists( $exclude_dirs{ $pkg } ) )
	    {
		log_info( "Excluding module \"$mod\" from package \"$pkg\"" );
	    }
	    else
	    {
		log_debug( "Found module \"$mod\" from package \"$pkg\"" );
		$ycp_modules{ $mod } = $file;
	    }
	}
    }


    #
    # Find all YCP modules that are already installed
    #

    my @installed_modules = glob( $modules_dir . "/*.ycp" );

    foreach $file( @installed_modules )
    {
	$file = basename( $file );

	if ( ! defined( $ycp_modules{ $file } ) )
	{
	    log_warning( "Module $file not found below $work_root" );
	}
    }


    #
    # Clear old YCP byte code files (.ybc)
    #

    log_progress( "Removing old byte code files in $modules_dir" );
    sudo_system_int( "rm -f $modules_dir/*.ybc" );

    log_progress( "Removing old byte code files in $work_root" );
    system_int( "find $work_root -name '*.ybc' -print0 | xargs -0 $sudo_cmd rm" );



    #
    # Clear old YCP module files (.ycp)
    #

    log_progress( "Removing old modules from $modules_dir" );
    sudo_system_int( "rm -f $modules_dir/*.ycp" );


    #
    # Copy modules from CVS tree to system
    #

    log_progress( "Installing " . scalar( keys( %ycp_modules ) ) . " YCP modules" );
    make_install_dir( $modules_dir );
    xargs_install( $modules_dir, values( %ycp_modules ) );


    #
    # Find all 'include' statements in the newly copied modules
    #

    my $module;

    foreach $module( keys( %ycp_modules ) )
    {
	find_include_statements( $modules_dir . "/" . $module );
    }

    log_progress( "Modules require " . scalar( keys( %needed_ycp_include_files ) ) . " YCP include files" );

    #
    # Now find and install all include files required by the installed YCP modules
    #

    locate_include_files();
    install_include_files();


    #
    # Figure out YCP compilation order and compile the modules
    #

    my @order = calc_ycp_compilation_order();
    byte_compile( @order );


    log_milestone( "--- Byte-compiling YCP modules done.\n" );
}


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

# Byte-compile leftover YCP modules - everything that didn't get compiled by
# byte_compile_modules(), maybe because 'ycpc' in calc_ycp_compilation_order
# did not report the correct compilation order.

sub byte_compile_leftover_modules()
{
    log_milestone( "\n--- Phase 2b (-l): Byte-compiling leftover YCP modules..." );

    my @missing;
    my $missing_count	  = 0;
    my $old_missing_count = 0;
    my $loop_count	  = 0;

    do
    {
	$loop_count++;
	$old_missing_count = $missing_count;
	@missing = ();

	chdir( $modules_dir );
	my @src_modules		= <*.ycp>;
	my @compiled_modules	= <*.ybc>;

	my %compiled;
	my $mod;

	foreach $mod ( @compiled_modules )
	{
	    $mod = basename( $mod, ".ybc" );
	    $compiled{ $mod } = 1;
	}


	foreach $mod ( @src_modules )
	{
	    $mod = basename( $mod, ".ycp" );

	    push( @missing, $mod )
		if ( ! defined( $compiled{ $mod } ) );
	}

	$missing_count = $#missing + 1;

	if ( $loop_count == 1 )
	{
	    log_milestone( "$missing_count modules left over" );
	    log_info( @missing );
	}
	else
	{
	    log_progress( "Iteration #" . $loop_count
			  . ": $missing_count modules left over" );
	}

	# Brute-force approach:
	#
	# Simply try to byte-compile all modules that are left over. 
	# Even though the compilation order is screwed, at least one of
	# should compile.  

	foreach $mod ( @missing )
	{
	    my $success = byte_compile( $mod );
	    log_progress( "$mod\tOK" ) if $success;
	}

	# Do this as long as there are _any_ changes in the status.

    } while ( $missing_count != $old_missing_count );


    #
    # Print summary
    #

    if ( $missing_count > 0 )
    {
	my $mod;

	foreach $mod ( @missing )
	{
	    log_error( "Could not byte-compile $mod.ycp" );
	    # print FAILED_LOG "$mod.ycp\n";
	}

	push( @failed_dirs, $modules_dir );
    }
    else
    {
	log_progress( "All YCP modules successfully byte-compiled" );
    }

    chdir( $work_root );
}


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


# Build YCP packages.

sub make_ycp_packages()
{
    log_milestone( "\n--- Phase 3 (-y): Building YCP packages..." );

    #
    # Set up hash with packages not to process
    #

    my %dont_process = %exclude_dirs;

    # Add the contents of @core_binaries to that hash
    @dont_process{ @core_binaries } = (); # Black magic - see Perl cook book chapter 4.7


    # Remove the contents of @core_and_ycp from %dont_process

    my $pkg;

    foreach $pkg ( @core_and_ycp )
    {
	undef( $dont_process{ $pkg } );
    }


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

    #
    # Set up list with packages to process
    #

    my @packages = @ycp_favourites;

    if ( ! $favourites_only )
    {
	chdir( $work_root );
	push( @packages, sort( glob( "*" )  ) );
    }

    #
    # Process packages
    #

    foreach $pkg ( @packages )
    {
	my $dir = $work_root . "/" . $pkg;

	if ( -d $dir )
	{
	    if ( ! exists( $dont_process{ $pkg } ) )
	    {
		# log_progress( "Building $pkg" );
		make( $dir );
	    }
	    else
	    {
		if ( exists( $exclude_dirs{ $pkg } ) )
		{
		    log_progress( "\nExcluding $pkg\n" );
		}
	    }
	}
    }

    log_milestone( "--- Building YCP packages done.\n" );
}


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

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

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

    chdir $work_root;
    chdir $pkg;

    log_progress( "Building $pkg" );


    # 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;
    $do_make_makefile_cvs = 1 unless -f "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" );

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

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

    #
    # make
    #

    if ( ! $error )
    {
	log_progress( "\tmake $make_target" );
	system_int( "make $make_target >>$make_log 2>&1" );

	if ( $CHILD_ERROR )
	{
	    log_error( "'make $make_target' failed for $pkg" );
	    $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" );

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


    #
    # Summary
    #

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

    chdir $work_root;
}


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

# Check if a file is a YCP module: Search it for 'module XY'.
#
# Parameters:
#	file name
#
# Return value:
#	1 if it is a YCP module,
#	0 if it is not.

sub is_ycp_module()
{
    my ( $file_name ) = @_;
    my $module_name = basename( $file_name, ".ycp" );

    # log_debug( "Checking mod candidate $module_name: $file_name" );
    open( MOD, $file_name ) or return 0;

    my $line;

    while ( $line = <MOD> )
    {
	# A valid YCP named "ABC" module must have a line
	#	module "ABC";

	if ( $line =~ /^\s*module\s+"$module_name"\s*;/ )
	{
	    # log_debug( "Found \'module \"$module_name\"\' in $file_name" );
	    close( MOD );
	    return 1;
	}
    }

    close( MOD );
    return 0;
}


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


# Find all 'include' statements in a file and add the include file to the
# global %needed_ycp_include_files hash.
#
# Parameters:
#	Filename

sub find_include_statements()
{
    my ( $ycp ) = @_;

    if ( ! open( YCP, $ycp ) )
    {
	log_error( "Can't open $ycp" );
	return;
    }

    my $line;

    while ( $line = <YCP> )
    {
	# Note: This search method will also find 'include' statements in
	# multi-line comments, but then this can be considered _very_ broken.
	# It should really be fixed in the YCP source.
	# Anyway, it doesn't hurt much: Maybe one include file too many will be
	# installed to /usr/share/YaST2/include/ - so what...

	if ( $line =~ /^\s*include\s+"(.*)"/ )
	{
	    my $include_file = $1;
	    $needed_ycp_include_files{ $include_file } = undef;
	    log_info( basename( $ycp ) . " includes \"" . $include_file . "\"" );
	    $included_by{ $include_file } .= basename ( $ycp ) . " " ;
	}
    }

    close( YCP );
}


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


# Locate all include files in the global %needed_ycp_include_files hash in the
# CVS or Subversion working directory - write the full path into the hash.

sub locate_include_files()
{
    log_progress( "Searching include files" );

    # Grep all Makefile.am files to find out where include files get installed to.
    # This is necessary since somebody came up with that crazy idea to give
    # lots of include files the same name, differing only in install directory,
    # which has no match at all in the source directory structure. What a mess.

    find_install_dirs();


    # Cache YCP files for faster search

    log_progress( "Creating .ycp file location cache" );
    my @ycp_files = find_files( $work_root, "*.ycp", "(testsuite|examples|skeleton)" );


    log_progress( "Mapping include file paths" );

    #
    # Map all full path names to install directories, if possible
    # (i.e., if their Makefile.am had a line with @yncludedir@);
    # if not, it is most likely not an include file and can be ignored here.
    # If it is anyway, its Makefile.am is broken and needs to be fixed.
    #

    my %found_ycp_include_files;
    my $ycp_file;

    foreach $ycp_file ( @ycp_files )
    {
	my $dir = dirname( $ycp_file );

	if ( defined( $ycp_install_dirs{ $dir } ) )
	{
	    my $target = $ycp_install_dirs{ $dir };
	    $target .= "/" . basename( $ycp_file );
	    $found_ycp_include_files{ $target } = $ycp_file;

	    log_info( "Include file " . $target . " is " . $ycp_file );
	}
    }


    my $not_found = 0;
    my $needed_include;

    foreach $needed_include ( keys( %needed_ycp_include_files ) )
    {
	if ( ! defined( $found_ycp_include_files{ $needed_include } ) )
	{
	    $not_found++;
	    log_error( "Can't find include file \"" . $needed_include
		  . "\"\n    included by " . $included_by{ $needed_include } );
	}
	else
	{
	    log_debug( "Found " . $needed_include );

	    $needed_ycp_include_files{ $needed_include } =
		$found_ycp_include_files{ $needed_include };
	}
    }

    if ( $not_found > 0 )
    {
	log_error( "Can't resolve YCP include dependencies" );
    }
    else
    {
	log_progress( "Found all YCP files included from modules" );
    }
}


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


# Install all YCP include files in the global %needed_ycp_include_files hash.

sub install_include_files()
{
    log_progress( "Installing include files" );
    my $ycp_include_file;

    foreach $ycp_include_file ( keys( %needed_ycp_include_files ) )
    {
	if ( defined( $needed_ycp_include_files{ $ycp_include_file } ) )
	{
	    my $src = $needed_ycp_include_files{ $ycp_include_file };
	    my $target = $ycp_include_dir . "/" . $ycp_include_file;

	    install_file( $src, $target );
	}
	else
	{
	    log_warning( "Skipping $ycp_include_file - not found" );
	}
    }
}


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

# Install multiple files like "xargs": Build a command line as long as possible
# to avoid to many shell calls. Calls
#
#	sudo install -m 644 <file> [<file>...] <target>
#
# until all files are installed.
#
# Parameters:
#	target directory
#	(array) files to install

sub xargs_install()
{
    my $target_dir = shift;
    my @files = @_;


    my $cmd_line_limit = 8000;
    my $sources = "";

    while ( $#files >= 0 )
    {
	my $file = shift @files;

	if ( length( $sources ) + length( $file ) > $cmd_line_limit )
	{
	    # Buffer is full - execute command
	    # (avoid "command line too long" error)

	    install_file( $sources, $target_dir );
	    $sources = "";
	}

	$sources .= " " . $file;
    }

    #
    # Handle any leftovers
    #

    if ( ! $sources =~ /^\s*$/ )
    {
	install_file( $sources, $target_dir );
    }
}


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

# Install "src" to "target" with proper permissions (644).
#
# Parameters:
#	source (may be multiple source files as one string)
#	target (may be a directory

sub install_file()
{
    my ( $src, $target ) = @_;
    my $cmd = "$install_cmd -p -m 644 $src $target";

    log_info( "$sudo_cmd $cmd" );
    sudo_system_int( $cmd );
}


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

# Create an installation directory with all its component with mode 755.
#
# Parameters:
#	Directory to create

sub make_install_dir()
{
    my ( $dir ) = @_;
    my $cmd = "$install_cmd -m 755 -d $dir";

    log_info( "$sudo_cmd $cmd" );
    sudo_system_int( $cmd );
}


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


# Since some of our developers in their infinite wisdom decided long ago that
# it might be a cool thing to try what happens when source file names are not
# unique, we now have to deal with dozens of YCP include files with the same
# name, sometimes even in the same source directory level. The only thing to
# tell them apart is the install target in the respective Makefile.am, so now
# we have to 'grep' through all of them and find lines with "@yncludedir" to
# figure out the installation target directory.
#
# Yes, this is broken by design.
#
# This function now searches all Makefile.am files in the CVS or SVN working directory
# for lines with @yncludedir@ and fills the global %ycp_install_dirs hash with
# the result. The keys of this hash are directories in the CVS or SVN working tree,
# the values are the target directories below /usr/share/YaST2/include
# (with the @yncludedir@ macro removed, e.g. "network/services" ).

sub find_install_dirs()
{
    log_progress( "Searching target directories for YCP include files" );

    my $makefile_am;

    foreach $makefile_am ( find_files( $work_root, "Makefile.am", "(testsuite|examples|skeleton)" ) )
    {
	if ( ! open( MAKEFILE_AM, $makefile_am ) )
	{
	    log_error( "Can't open $makefile_am" );
	    return;
	}

	my $dir = dirname( $makefile_am );
	my $line;

	while ( $line = <MAKEFILE_AM> )
	{
	    $line =~ s/#.*//;

	    if ( $line =~ /\@yncludedir\@/ )
	    {
		$line =~ s:^.*\@yncludedir\@/?::;  # strip everything up to @yncludedir@/
		$line =~ s/\s*$//;		   # strip trailing whitespace

		if ( defined( $ycp_install_dirs{ $dir } ) )
		{
		    log_error( "Include target in $makefile_am not unique!" );
		}
		else
		{
		    $ycp_install_dirs{ $dir } = $line;
		    log_debug( "Dir " . $dir . " => " . $line );
		}
	    }
	}

	close( MAKEFILE_AM );
    }
}


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

# Let the YCP compiler calculate the compilation order for the YCP modules,
# taking their interdependencies into account.
#
# Parameters:
#	---
# Return value:
#	Module list in compilation order

sub calc_ycp_compilation_order()
{
    log_progress( "Calculating YCP module compilation order" );

    chdir( $modules_dir );
    my $cmd = "$ycpc_cmd -f -r -I $ycp_include_dir *.ycp 2>>$make_log | egrep -v '(^Check)|(^\$)|(is newer than)'";

    log_info( "$cmd" );
    my @order = split( "\n", `$cmd` );

    log_debug( "YCP compilation order:\n", join( "\t\n", @order ) );
    chdir( $work_root );

    return @order;
}


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

# Byte-compile a list of YCP modules in this order.
#
# Parameters:
#	YCP module list
#
# Return value:
#	1 if success
#	0 if (any) error

sub byte_compile()
{
    my @modules = @_;

    if ( $#modules > 0 )
    {
	log_progress( "Byte-compiling " . ( $#modules + 1 ) . " YCP modules" );
    }

    chdir( $modules_dir );

    my $module;
    my $success = 1;

    foreach $module ( @modules )
    {

	$module .= ".ycp";

	my $cmd = "$ycpc_cmd -I $ycp_include_dir -M $modules_dir -q -c $modules_dir/$module >>$make_log 2>&1";

	log_debug( "$sudo_cmd $cmd" );
	my $result = sudo_system_int( $cmd );

	log_debug( "Byte-compiling $module\t- "
		   . ($result == 0 ? "Success" : "Error" ) );

	$success = 0 if $result != 0;
    }

    chdir( $work_root );

    return $success;
}


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


sub print_summary()
{
    log_milestone( "\n$prog_name $make_target summary:\n" );
    log_milestone( "    Success in $successful_dirs directories" );

    if ( $#failed_dirs >= 0 )
    {
	log_milestone( "    Failed  in " . ( $#failed_dirs+1 ) . " directories:\n" );

	my $failed_dir;

	foreach $failed_dir ( sort @failed_dirs )
	{
	    log_milestone( "\t$failed_dir" );
	}
    }
    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;
}

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

# 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 "Usage: $prog_name [opt] [make-target]\n"				.
	"\n"									.
	"Builds all YaST2 sources from CVS or Subversion.\n"					.
	"Call this from your toplevel yast2/source CVS or SVN working directory!\n"	.
	"\n"									.
	"\t-a all (default) - same as -bmy if none of -b -m -l -y is given\n"	.
	"\t-b binaries - make core binaries\n"					.
	"\t-m modules - byte-compile YCP modules\n"				.
	"\t-l leftovers - byte-compile leftover YCP modules\n"			.
	"\t-y YCP - make YCP packages (includes -l)\n"				.
	"\t-u favourites only - stop processing after favourites list is done\n".
	"\t-f fast - no \"make -f Makefile.cvs\" if it can be avoided\n"	.
	"\t-p <prefix> - use <prefix> instead of /usr\n"			.
	"\t-n no-sudo - useful with -p, do not sudo certain commands\n"		.
	"\n"									.
	"\t-v verbose\n"							.
	"\t-d debug - even more verbose\n"					.
	"\t-s silent - turn verbosity down\n"					.
	"\t-h help (this message)\n"						.
	"\n"									.
	"EXAMPLES:\n"								.
	"\n"									.
	"Build everything:\n"							.
	"\t$prog_name\n"							.
	"\n"									.
	"Do a \"make pot\" in all YCP packages:\n"				.
	"\t$prog_name -y pot\n"							.
	"\n"									.
	"Byte-recompile all YCP modules, then make all YCP packages, but\n"	.
	"don't do \"make -f Makefile.cvs\" if it can be avoided:\n"		.
	"\t$prog_name -myf\n"							.
	"\n"									.
	"Default values can be overridden in $user_config_file\n"		.
	"Remember to look into $make_log for build errors!\n"			.
	"";
}



# EOF
