#!/usr/pkg/bin/perl
############################################################################
# $Id: tmake,v 1.80 1999/12/07 01:38:20 paul Exp $
#
# Creates a Makefile from a template and a project file.
#
# Copyright (C) 1996-2000 by Trolltech AS.  All rights reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted, provided
# that this copyright notice appears in all copies.
# No representations are made about the suitability of this software for any
# purpose. It is provided "as is" without express or implied warranty.
#
#
# Some important, global variables in tmake:
#   cpp_ext	C++ extension added to moc output (.cpp)
#   obj_ext	Object file extension (.o on Unix, .obj otherwise)
#   moc_aware	Will scan for files containing Qt signals/slots
#   moc_pre	Moc prefix for generated moc file: x.h -> moc_x.cpp
#   moc_ext	Moc extension for generated moc file: x.cpp -> x.moc
#   moc_cmd	The moc command in your makefile, $(MOC)
#   uic_decl	Uic extension for generated declaration file: x.ui -> x.h
#   uic_impl	Uic extension for generated implementation file: x.ui -> x.cpp
#   uic_cmd	The uic command in your makefile, $(UIC)
#   uic_decl_path	The path where the generated decl files from uic files are created, $(INTERFACE_DECL_PATH)
#   linebreak	Line break character (\)
#   dir_sep	Directory separator (/ on Unix, \ on Windows)
#   is_unix	Autodetected. If not Unix, assume Windows (Win32).
#
# If you need to customize any of these settings, do it before
# calling StdInit() in the template file.
#
############################################################################

$TMAKE_VERSION = "1.13";

if ($] < 5.0) {
    &tmake_error("This program requires perl version 5 or newer");
}

$cpp_ext	= "cpp";
$moc_aware	= 0;
$moc_pre	= "moc_";
$moc_ext	= "moc";
$moc_cmd	= '$(MOC)';
$uic_decl	= "h";
$uic_impl	= "cpp";
$uic_cmd	= '$(UIC)';
$uic_decl_path = '$(INTERFACE_DECL_PATH)';
%uic_dependency	= ();
$linebreak	= "\\";
$really_unix	= &check_unix();
$is_unix	= $really_unix;
$dir_sep	= $is_unix ? "/" : "\\";
$obj_ext	= $is_unix ? "o" : "obj";
$depend_path	= "";
$nodepend	= 0;
$output_count	= 0;
$notrim_whitespace = 0;
$read_tmakeconf	= 0;

$template_name	= "";
$project_name	= "";
$outfile	= "";
%project	= ();
$eval_quit	= 0;

$project{"TMAKEPATH"} = $ENV{"TMAKEPATH"} . ";" . $ENV{"HOME"} . "/.tmake/;/usr/pkg/lib/tmake/netbsd-g++";

while ( @ARGV ) {				# parse command line args
    $_ = shift @ARGV;
    if ( s/^-// ) {
	if ( /^e(.*)/ ) {
	    if ( ! $read_tmakeconf ) {
		$read_tmakeconf = 1;
		&ScanProject( &find_template("tmake.conf") );
	    }
	    $text = "";
	    eval(  ($1 eq "") ? shift @ARGV : $1 );
	    die $@ if $@;
	    print $text . "\n" if ($text ne "");
	    $eval_quit = 1;
	} elsif ( /^t(.*)/ ) {
	    $template_name = ($1 eq "") ? shift @ARGV : $1;
	} elsif ( /^o(.*)/ ) {
	    $outfile = ($1 eq "") ? shift @ARGV : $1;
	    ($outfile eq "-") && ($outfile = "");
	    if ( $outfile ne "" ) {
		open(STDOUT,">" . &fix_path($outfile)) ||
		  &tmake_error("Cannot create \"$outfile\": $!");
	    }
	    $project{"OUTFILE"} = $outfile;
	} elsif ( /^p(.*)/ ) {
	    #
	    # The -p option is obsolete and will be removed in the next
	    # tmake release.
	    #
	    &tmake_warning( "-p option obsolete, instead use \"tmake file1.pro file2.pro ...\"");
	    my($pf) = ($1 eq "") ? shift @ARGV : $1;
	    if ( ! $read_tmakeconf ) {
		$read_tmakeconf = 1;
		&ScanProject( &find_template("tmake.conf") );
	    }
	    if ( ! ($pf =~ /\.pro$/i) && -f &fix_path($pf . ".pro") ) {
		$pf .= ".pro";
	    }
	    if ( $project_name eq "" ) {
		$project_name = $pf;
		$project{"PROJECT"} = $project_name;
		$project{"PROJECT"} =~ s/\.pro$//i;
		$project{"TARGET"} = $project{"PROJECT"};
	    }
	    if ( !&ScanProject($pf) ) {
		&tmake_error("Cannot open project file \"$pf\"");
	    }
	} elsif ( /^unix$/ ) {
	    $is_unix = 1;
	    $dir_sep = "/";
	    $obj_ext = "o";
	} elsif ( /^win32$/ ) {
	    $is_unix = 0;
	    $dir_sep = "\\";
	    $obj_ext = "obj";
	} elsif ( /^nodepend$/ ) {
	    $nodepend = 1;			# don't generate dependencies
	} elsif ( /^v$/ ) {
	    $verbose = 1;
	} else {
	    &tmake_usage();
	}
    } elsif ( /^\s*((?:[^:\s]*?:)?)(\w+)\s*(\+=|\*=|\-=|\/=|=)/ ) {
	my($arg) = $_;
	if ( ! $read_tmakeconf && ! (/^\s*TMAKEPATH/) ) {
	    $read_tmakeconf = 1;
	    &ScanProject( &find_template("tmake.conf") );
	}
	Project( $arg );			# manual project setting
    } else {
	my($pf) = $_;
	if ( ! $read_tmakeconf ) {
	    $read_tmakeconf = 1;
	    &ScanProject( &find_template("tmake.conf") );
	}
	if ( ! ($pf =~ /\.pro$/i) && -f &fix_path($pf . ".pro") ) {
	    $pf .= ".pro";
	}
	if ( $project_name eq "" ) {
	    $project_name = $pf;
	    $project{"PROJECT"} = $project_name;
	    $project{"PROJECT"} =~ s/\.pro$//i;
	    $project{"TARGET"} = $project{"PROJECT"};
	}
	if ( !&ScanProject($pf) ) {
	    &tmake_error("Cannot open project file \"$pf\"");
	}
    }
}

&tmake_verb("Version $TMAKE_VERSION (runtime environment: " .
	    ($really_unix ? "Unix" : "Win32") . ")\n" );

if ( $eval_quit ) {
    &tmake_verb("Done!");
    exit 0;
}
($project_name eq "") && &tmake_usage();

if ( $template_name eq "" ) {
    $template_name = $project{"TEMPLATE"} ?
	    $project{"TEMPLATE"} : "default.t";
}
$template_name = &find_template($template_name);
&IncludeTemplate($template_name);
&tmake_verb("Done!");
exit 0;						# finished!



##############################################################################
# Subroutines from here
##############################################################################

#
# tmake_usage()
#
# Prints a message about program usage and exits
#

sub tmake_usage {
    print STDERR "Usage:\n    tmake [options] project-files\n";
    print STDERR "Options:\n";
    print STDERR "    -e expr    Evaluate expression, ignore template file\n";
    print STDERR "    -nodepend  Don't generate dependency information\n";
    print STDERR "    -o file    Write output to file\n";
    print STDERR "    -t file    Specify a template file\n";
    print STDERR "    -unix      Create output for Unix (auto detects)\n";
    print STDERR "    -v         Verbose/debug mode\n";
    print STDERR "    -win32     Create output for Win32 (auto detects)\n";
    exit 1;
}


#
# tmake_error(msg)
#
# Prints the message and exits
#

sub tmake_error {
    my($msg) = @_;
    print STDERR "tmake error: " . $msg . "\n";
    exit 1;
}


#
# tmake_warning(msg)
#
# Prints the warning message
#

sub tmake_warning {
    my($msg) = @_;
    print STDERR "tmake warning: " . $msg . "\n";
}


#
# tmake_verb()
#
# Prints a verbose message
#

sub tmake_verb {
    my($msg) = @_;
    $verbose && print STDERR "tmake: " . $msg . "\n";
}


#
# check_unix()
#
# Returns 1 if this is a Unix, 0 otherwise.
#

sub check_unix {
    my($r);
    $r = 0;
    if ( -f "/bin/uname" ) {
	$r = 1;
	(-f "\\bin\\uname") && ($r = 0);
    }
    if ( -f "/usr/bin/uname" ) {
	$r = 1;
	(-f "\\usr\\bin\\uname") && ($r = 0);
    }
    return $r;
}


#
# find_template(filename)
#
# Looks for the template file.
#   1.	search the current directory
#   2.	search the directories in TMAKEPATH
#   3.	search in $HOME/.tmake
#

sub find_template {
    my($filename) = @_;
    my($tb,$d,$p,@dirs);
    if ( !defined($template_base) || ($template_base eq "") ) {
	$tb = "";
    } else {
	$tb = $template_base . ";";
    }
    $d = $tb . $project{"TMAKEPATH"};
    @dirs = ("");
    push @dirs, &split_path( $d );
    $filename .= ".t" unless ($filename =~ /\.\w+$/);
    for $d ( @dirs ) {
	$p = $d . $filename;
	if ( -f &fix_path($p) ) {
	    if ( $filename eq "tmake.conf" ) {
		$tmake_platform = $d;
		$tmake_platform =~ s-.*[/\\]([^/\\]*)[/\\]-$1-;
		&tmake_verb("Detected platform $tmake_platform");
	    }
	    return $p;
	}
	return ($d . $filename) if ( -f &fix_path($d . $filename) );
    }
    &tmake_error("Template file " . $filename . " not found");
}


##############################################################################
# User functions
##############################################################################

#
# StdInit()
#
# Standard initialization
#

sub StdInit {
    my($p);
    return if $stdinit_done;
    $stdinit_done = 1;
    if ( defined($project{"OBJECTS_DIR"}) ) {
	$project{"OBJECTS_DIR"} = FixPath($project{"OBJECTS_DIR"});
	&mkdirp($project{"OBJECTS_DIR"},0777);
    }
    if ( defined($project{"MOC_DIR"}) ) {
	$project{"MOC_DIR"} = FixPath($project{"MOC_DIR"});
	&mkdirp($project{"MOC_DIR"},0777);
    }
    if ( defined($project{"DESTDIR"}) ) {
	$project{"DESTDIR"} = FixPath($project{"DESTDIR"});
	&mkdirp($project{"DESTDIR"},0777);
    }

    $project{"OBJECTS"} = &Objects($project{"SOURCES"});


    $project{"UICDECLS"}=  &UicDecls($project{"INTERFACES"});
    $project{"UICIMPLS"}=  &UicImpls($project{"INTERFACES"});
    $project{"UICOBJECTS"}=  &Objects($project{"INTERFACES"});
    #    $project{"HEADERS"}.=  " " . &UicDecls($project{"INTERFACES"});
    #    $project{"SOURCES"}.=  " " . &UicImpls($project{"INTERFACES"});
    $project{"OBJECTS"}.=  " " . &Objects($project{"INTERFACES"});

    foreach $m ( $project{"INTERFACES"} ) {
	$decl = $m;
	$decl =~ s/\.ui$/\.$uic_decl/;
	$impl = $m;
	$impl =~ s/\.ui$/\.$uic_impl/;
	$uic_dependency{ $impl } = $decl . " ${linebreak}\n\t\t" . $m;
    }

    if ( $moc_aware ) {
	$project{"_HDRMOC"} = &list_moc($project{"HEADERS"},$moc_pre,$cpp_ext);
        $project{"_SRCMOC"} = &list_moc($project{"SOURCES"},"",$moc_ext);
	$project{"OBJMOC"}  = &Objects($project{"_HDRMOC"});

	$p = $project{"OBJMOC"} . " " . &Objects( &MocSources($project{"INTERFACES"} ) );
	$p =~ s/^\s+//;
	$p =~ s/\s+$//;
	$project{"OBJMOC"} = $p;

	#$p = $project{"SRCMOC"} . " " . &MocSources($project{"INTERFACES"} );

	$p = $project{"_HDRMOC"} . " " . &MocSources($project{"INTERFACES"} ) . " " . $project{"_SRCMOC"};
	$p =~ s/^\s+//;
	$p =~ s/\s+$//;
	$project{"SRCMOC"} = $p;
    }

    &AddIncludePath("");
}

sub FixPath {
    my($p) = @_;
    if ( !defined($p) || ($p eq "") || ($p eq ".") ) {
	$p = "";
    } else {
	$p .= $dir_sep;
	$p =~ s-[\\/]+-${dir_sep}-g;
    }
    return $p;
}


#
# Config(name)
#
# Returns true if the project variable CONFIG contains the
# configuration name.
#

sub Config {
    my($name) = @_;
    return $project{"CONFIG"} =~ /\b\Q$name\E\b/;
}


#
# DisableOutput()
#
# Disables tmake output.  Must be restored by calling a corresponding
# EnableOutput().
#

sub DisableOutput {
    $output_count++;
}


#
# EnableOutput()
#
# Enables tmake output again after DisableOutput() has been called.
#

sub EnableOutput {
    $output_count--;
}


#
# Now() - sets $text
#
# Sets $text to the current date and time.
#

sub Now {
    my($sec,$min,$hour,$mday,$mon,$year);
    ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
    $text = sprintf("%02d:%02d, %4d/%02d/%02d",
		    $hour, $min, 1900+$year, 1+$mon, $mday);
}


#
# expand_project_var(var)
#
# Internal function for Project().
# Expands a project value string.
#

sub expand_project_var {
    my($v) = @_;
    my($c);
    return "" if !defined($v);
    $c = 0;
    while ( $c < 100 ) {			# expand $$
	if ( $v =~ s/(\$\$\w+)/\035/ ) {
	    $_ = $1;
	    s/\$\$//g;
	    if ( !defined($project{$_}) ) {
		$v =~ s/\035//g;
	    } else {
		$v =~ s/\035/$project{$_}/g;
	    }
	    $c++;
	} else {
	    $c = 100;
	}
    }
    return $v;
}


#
# project_var_condition(cond)
#
# Internal function for Project().
# Checks the condition and returns 1 if it is met, otherwise 0.
# The condition can be a platform decriptor (e.g. win32,unix), a
# possible CONFIG item (e.g. release) or several condition vars
# ANDed (':') together.
#
# Examples:
#    win32:SOURCES += win32specific.cpp
#    unix:debug:SOURCES += unixdebug.cpp

sub project_var_condition {
    my($c) = @_;
    return 1 if !defined($c) || ($c eq "");
    if ( $c =~ /(^.*?):(.*$)/ ) {
	my($c1,$c2) = ($1,$2);
	return &project_var_condition($c1) &&
	       &project_var_condition($c2);
    } else {
	if ( $c eq "unix" ) {
	    return 0 if !$is_unix;
	} elsif ( $c eq "win32" ) {
	    return 0 if $is_unix;
	} elsif ( ($c ne $tmake_platform) && !Config($c) ) {
	    return 0;
	}
    }
    return 1;
}


#
# Project(strings)
#
# This is a powerful function for setting or reading project variables.
# Returns the resulting project variables (joined with space between).
#
# Get a project variable:
#   $s = Project("TEMPLATE");	    -> $s = "TEMPLATE"
#
# Set a project variable:
#   Project("TEMPLATE = lib");	    -> TEMPLATE = lib
#   Project("CONFIG =";)	    -> CONFIG empty
#
# Append to a project variable:
#   Project("CONFIG = qt");	    -> CONFIG = qt
#   Project("CONFIG += debug");	    -> CONFIG = qt debug
#
# Append to a project variable if it does not contain the value already:
#   Project("CONFIG = qt release"); -> CONFIG = qt release
#   Project("CONFIG *= qt");	    -> CONFIG = qt release
#   Project("CONFIG *= opengl");    -> CONFIG = qt release opengl
#
# Subtract from a project variable:
#   Project("THINGS = abc xyz");    -> THINGS = abc xyz
#   Project("THINGS -= abc");	    -> THINGS = xyz
#
# Search/replace on a project variable:
#   Project("CONFIG = tq opengl");  -> CONFIG = tq opengl
#   Project("CONFIG /= s/tq/qt/");  -> CONFIG = qt opengl
#
# The operations can be performed on several project variables at a time.
#
#   Project("TEMPLATE = app", "CONFIG *= opengl", "THINGS += klm");
#

sub Project {
    my @settings = @_;
    my($r,$if_var,$t,$s,$v,$p,$c);
    $r = "";
    foreach ( @settings ) {
	$v = $_;
	if ( $v =~ s/^\s*((?:(?:[\w\-]+\s*:\s*){1,10})?)(\w+)\s*(\+=|\*=|\-=|\/=|=)// ) {
	    $if_var = $1;
	    if ( $if_var ne "" ) {
		$if_var =~ s/\s//g;
		chop $if_var;
		return "" if !&project_var_condition($if_var);
	    }
	    $t = $2;
	    $s = $3;
	    if ( ! $notrim_whitespace ) {
		$v =~ s/^\s+//;			# trim white space
		$v =~ s/\s+$//;
	    }
	    $v = expand_project_var($v);
	    $p = $project{$t};
	    if ( $s ne "=" && $v eq "" ) {
		# nothing to append, subtract or sed
	    } elsif ( $s eq "=" ) {		# set variable
		$p = $v;
	    } elsif ( $s eq "+=" ) {		# append
		if ( $p eq "" ) {
		    $p = $v;
		} else {
		    $p .= " " . $v;
		}
	    } elsif ( $s eq "*=" ) {		# append if not contained
		if ( !($p =~ /(?:^|\s)\Q$v\E(?:\s|$)/) ) {
		    if ( $p eq "" ) {
			$p = $v;
		    } else {
			$p .= " " . $v;
		    }
		}
	    } elsif ( $s eq "-=" ) {		# subtract
		$p =~ s/$v\s*//g;
	    } elsif ( $s eq "/=" ) {		# sed
		$cmd = '$p =~ ' . $v;
		eval $cmd;
	    }
	    $project{$t} = expand_project_var($p);
	} else {
	    $p = expand_project_var($project{$v});
	}
	if ( $p ne "" ) {
	    $r = ($r eq "") ? $p : ($r . " " . $p);
	}
    }
    return $r;
}


#
# Substitute(string)
#
# This function substitutes project variables in a text.
#
# Example:
#   Substitute('The project name is "$$PROJECT"')
#

sub Substitute {
    my($subst) = @_;
    $text = expand_project_var($subst);
    return $text;
}


#
# ScanProject(file)
#
# Scans a project file. Inserts project variables into the global
# associative project array.
#

sub ScanProject {
    my($file) = @_;
    my($var,$val,@v,$more,$line,$endmark);

    $var = "";
    $line = 0;
    open(TMP,&fix_path($file)) || return 0;

    &tmake_verb("Reading the project file $file");
    while ( <TMP> ) {
	$line++;
	s/\#.*//;				# strip comment
	s/^\s+//;				# strip white space
	s/\s+$//;
	if ( /^\s*((?:(?:(?:[\w\-]+\s*:\s*){1,10})?)\w+\s*(\+|\-|\*|\/)?=)/ ) {
	    $var = $1;				# var also contains the ".="
	    s/^.*?=\s*//;
	    if ( /^\<\<(.*)$/ ) {
		$endmark = $1;
		$val = "";
		while ( <TMP> ) {
		    $line++;
		    if ( /^\Q$endmark\E$/ ) {
			$endmark = "";
			last;
		    }
		    $val .= $_;
		}
		if ( $endmark ne "" ) {
		    tmake_error("$file:$line: End marker $endmark not found");
		}
		chop $val if ( $val ne "" );
		$notrim_whitespace++;
		Project( $var . $val );
		$notrim_whitespace--;
		$var = "";
		$_ = "";
	    }
	}
	if ( $var ne "" ) {
	    $more = ( $_ =~ s/\s*\\\s*$// );	# more if \ at end of line
	    push( @v, split( /\s+/, $_ ) );
	    if ( ! $more ) {
		$val = join(" ",@v);
		Project( $var . $val );
		$var = "";
		@v = ();
	    }
	} elsif ( $_ ne "" ) {
	    tmake_error("$file:$line: Syntax error");
	}
    }
    close(TMP);
    &tmake_verb("Done reading the project file $file");
    return 1;
}


#
# IncludeTemplate(template_name)
#
# Includes and processes a template file.
#
# Below, we read the template file and executes any perl code found.
# Perl code comes after "#$". The variable $text contains the text
# to replace the perl code that was executed.
# Template comments begin with "#!".
#

sub IncludeTemplate {
    my($t_name) = @_;
    my($cmd,$cmd_block,$cmd_end,$is_cmd_block,$saveline,$spaceonly);
    local($text);
    local(*T);

    $t_name = &find_template($t_name);
    if ( $tmake_template_dict{$t_name} ) {
	&tmake_error("Cyclic template inclusion for $t_name");
    } else {
	$tmake_template_dict{$t_name} = 1;
    }
    $template_base = $t_name;
    $template_base =~ s-(.*[/\\]).*-$1-;
    &tmake_verb("Reading the template $t_name");
    open(T,&fix_path($t_name)) ||
	&tmake_error("Cannot open template file \"$t_name\"");

    while ( <T> ) {
	if ( /\#\!/ ) {				# tmake comment
	    s/\s*\#\!.*//;
	    next if /^$/;
	}
	if ( /\#\$(\{)?\s*(.*)\n/ ) {		# code
	    $cmd = $2;
	    $is_cmd_block = defined($1) && ($1 eq "{");
	    s/\#\$.*\n//;
	    if ( $is_cmd_block ) {		# code block #${ ...
		$saveline = $_;
		$cmd_block = $cmd;
		$cmd_end = 0;
		while ( <T> ) {
		    $cmd = $_;
		    $cmd =~ s/\s*\#\!.*//;	# tmake comment
		    if ( $cmd =~ /^\s*\#\$\}/ ) {
			$_ = "";
			$cmd_end = 1;
			last;
		    }
		    $cmd =~ s/^\s*\#(\$)?\s*//;
		    $cmd_block .= $cmd;
		}
		$cmd_end || &tmake_error('#$} expected but not found');
		$cmd = $cmd_block;
		$_ = $saveline;
	    }
	    $spaceonly = /^\s*$/;
	    $saveline = $_;
	    &tmake_verb("Evaluate: $cmd");
	    $text = "";
	    eval $cmd;
	    die $@ if $@;
	    next if $spaceonly && ($text =~ /^\s*$/);
	    print $saveline . $text . "\n" if  $output_count <= 0;
	} else {				# something else
	    print if $output_count <= 0;
	}
    }
    close( T );
}


#
# Expand(var) - appends to $text
#
# Expands a list of $project{} variables with a space character between them.
#

sub Expand {
    my @vars = @_;
    my($t);
    $t = Project(@vars);
    if ( $text eq "" ) {
	$text = $t;
    } elsif ( $t ne "" ) {
	$text .= " " . $t;
    }
    return $text;
}


#
# ExpandGlue(var,prepend,glue,append) - appends to $text
#
# Expands a $project{} variable, splits on whitespace
# and joins with $glue. $prepend is put at the start
# of the string and $append is put at the end of the
# string. The resulting string becomes "" if the project
# var is empty or not defined.
#
# Example:
#
#   The project file defines:
#	SOURCES = a b c
#
#   ExpandGlue("SOURCES","<","-",">")
#
#   The result:
#	$text = "<a-b-c>"
#

sub ExpandGlue {
    my($var,$prepend,$glue,$append) = @_;
    my($t,$v);
    $v = Project($var);
    if ( $v eq "" ) {
	$t = "";
    } else {
	$t = $prepend . join($glue,split(/\s+/,$v)) . $append;
    }
    if ( $text eq "" ) {
	$text = $t;
    } elsif ( $t ne "" ) {
	$text .= " " . $t;
    }
    return $text;
}


#
# ExpandList(var) - sets $text.
#
# Suitable for expanding HEADERS = ... etc. in a Makefile
#

sub ExpandList {
    my($var) = @_;
    return ExpandGlue($var,""," ${linebreak}\n\t\t","");
}


#
# ExpandPath(var,prepend,glue,append) - appends to $text
#
# Expands a $project{} variable, splits on either ';' or
# whitespace and joins with $glue. $prepend is put at the
# start of the string and $append is put at the end of the
# string. The resulting string becomes "" if the project
# variable is empty or not defined.
#
# If the variable contains at least one semicolon or tmake
# is running on Windows, the resulting items are put in
# double-quotes.
#
# Example:
#
#   The project file defines:
#	INCLUDEPATH = "C:\qt\include;c:\program files\msdev\include
#
#   ExpandPath("INCLUDEPATH","-I","-I","")
#
#   The result:
#	$text = -I"c:\qt\include" -I"c:\program files\msdev\include"
#

sub ExpandPath {
    my($var,$prepend,$glue,$append) = @_;
    my($t,$v);
    my($s);
    $v = Project($var);
    if ( $v eq "" ) {
	$t = "";
    } else {
	if ( $v =~ /;/ || !$is_unix ) {
	    $prepend .= '"';
	    $glue = '"' . $glue . '"';
	    $append = '"' . $append;
	}

	if ( $v =~ /;/ ) {
	    $t = $prepend . join($glue,split(/;+/,$v)) . $append;
	} else {
	    $t = $prepend . join($glue,split(/\s+/,$v)) . $append;
	}
    }
    if ( $text eq "" ) {
	$text = $t;
    } elsif ( $t ne "" ) {
	$text .= " " . $t;
    }
    return $text;
}


#
# TmakeSelf()
#
# Generates makefile rule to regenerate the makefile using tmake.
#

sub TmakeSelf {
    my $a = "tmake $project_name";
    if ( $nodepend ) {
	$a .= " -nodepend";
    }
    if ( $outfile ) {
	$text = "tmake: $outfile\n\n$outfile: $project_name\n\t";
	$a .= " -o $outfile";
    } else {
	$text = "tmake:\n\t";
    }
    $text .= $a
}


#
# Objects(files)
#
# Replaces any extension with .o ($obj_ext).
#

sub Objects {
    local($_) = @_;
    my(@a);
    @a = split(/\s+/,$_);
    foreach ( @a ) {
	s-\.\w+$-.${obj_ext}-;
	if ( defined($project{"OBJECTS_DIR"}) ) {
	    s-^.*[\\/]--;
	    $_ = $project{"OBJECTS_DIR"} . $_;
	}
    }
    return join(" ",@a);
}

#
# UicImpls(files)
#
# Replaces any extension with .h ($uic_impl).
#

sub UicImpls {
    local($_) = @_;
    my(@a);
    @a = split(/\s+/,$_);
    foreach ( @a ) {
	s-\.\w+$-.${uic_impl}-;
    }
    return join(" ",@a);
}

#
# UicDecls(files)
#
# Replaces any extension with .cpp ($uic_decl).
#

sub UicDecls {
    local($_) = @_;
    my(@a);
    @a = split(/\s+/,$_);
    foreach ( @a ) {
	s-\.\w+$-.${uic_decl}-;
    }
    return join(" ",@a);
}

#
# MocSources(files)
#
# Only useful for uic files!
#
# Replaces any extension with .cpp ($cpp_ext) and adds moc_ ($moc_pre)
#

sub MocSources {
    local($_) = @_;
    my( @a, $t, $h, $f );
    @a = split(/\s+/,$_);
    foreach $t ( @a ) {
	$h = $t;
	$h =~ s-\.\w+$-.${uic_decl}-;
    $f = $t;
    $t =~ s-^(.*[/\\])?([^/\\]*?)\.(\w+)$-$1${moc_pre}$2.${cpp_ext}-;
	if ( defined($project{"MOC_DIR"}) ) {
	  $t =~ s-^.*[\\/]--;
	  $t = $project{"MOC_DIR"} . $t;
	}
	$moc_output{$h} = $t;
    $moc_input{$t}  = $h;
    }
    return join(" ",@a);
}

#
# list_moc(files,prefix,extension)
#
# Scans all files and selects all files that contain Q_OBJECT.
# Insert a prefix before the filename and replaces the filename extention.
#

sub list_moc {
    my($files,$pre,$ext) = @_;
    my(@v,@m,@lines,$contents,$n,$f,$t);
    @v = split(/\s+/,$files);
    undef $/;
    foreach $f ( @v ) {
	if ( open(TMP,&fix_path($f)) ) {
	    $contents = <TMP>;
	    close(TMP);
	    $n = 0;
	    @lines = split(/\n/,$contents);
	    grep( /tmake\s+ignore\s+Q_OBJECT/ && $n--, @lines );
	    $contents =~ s-//.*\n--g;		# strip C/C++ comments
	    $contents =~ s-/\*.*?\*/--gs;
	    @lines = split(/\n/,$contents);
	    grep( /(^|\W)(Q_OBJECT)|(Q_COMPONENT)(\W|$)/ && $n++, @lines );
	    if ( $n > 0 ) {
		$t = $f;
		$t =~ s-^(.*[/\\])?([^/\\]*?)\.(\w+)$-$1${pre}$2.${ext}-;
		if ( defined($project{"MOC_DIR"}) ) {
		    $t =~ s-^.*[\\/]--;
		    $t = $project{"MOC_DIR"} . $t;
		}
		$moc_output{$f} = $t;
		$moc_input{$t}	= $f;
		push(@m,$t);
	    }
	    $contents = "";
	}
    }
    $/ = "\n";
    return join(" ",@m);
}


#
# BuildObj(objects,sources)
#
# Builds the object files.
#

sub BuildObj {
    my($obj,$src) = @_;
    my(@objv,$srcv,$i,$s,$o,$d,$c,$comp,$cimp);
    @objv = split(/\s+/,$obj);
    @srcv = split(/\s+/,$src);
    for $i ( 0..$#objv ) {
	$s = $srcv[$i];
	$o = $objv[$i];
	next if $s eq "";
	$text .= $o . ": " . $s;
	if ( defined($moc_output{$s}) && ($moc_output{$s} ne "") ) {
	    $text .= " ${linebreak}\n\t\t" . $moc_output{$s};
	}
	if ( defined($uic_dependency{ $s } ) ) {
	    $d = $uic_dependency{ $s };
	} else {
	    $d = &make_depend($s);
	}
	$text .= " ${linebreak}\n\t\t" . $d if $d ne "";
	if ( ($s =~ /\.c$/) ) {
	    $comp = "TMAKE_RUN_CC";
	    $cimp = "TMAKE_RUN_CC_IMP";
	} else {
	    $comp = "TMAKE_RUN_CXX";
	    $cimp = "TMAKE_RUN_CXX_IMP";
	}
	if ( defined($project{"OBJECTS_DIR"}) ||
	     !defined($project{$cimp}) ) {
	    $c = $project{$comp};
	    $c =~ s/\$src/$s/;
	    $c =~ s/\$obj/$o/;
	    $text .= "\n\t$c";
	}
	$text .= "\n\n";
    }
    chomp $text;
}


#
# BuildMocObj(objects,sources)
#
# Builds the moc object files.
#

sub BuildMocObj {
    my($obj,$src) = @_;
    my(@objv,$srcv,$i,$s,$o,$hdr,$d);
    @objv = split(/\s+/,$obj);
    @srcv = split(/\s+/,$src);
    for $i ( 0..$#objv ) {
	$s = $srcv[$i];
	$o = $objv[$i];
	$hdr = $moc_input{$srcv[$i]};
	$text .= $o . ": " . $s . " ${linebreak}\n\t\t" . $hdr;
	$d = &make_depend($hdr);
	$text .= " ${linebreak}\n\t\t" . $d if $d ne "";
	if ( defined($project{"OBJECTS_DIR"}) || defined($project{"MOC_DIR"})||
	     !defined($project{"TMAKE_RUN_CXX_IMP"}) ) {
	    $c = $project{"TMAKE_RUN_CXX"};
	    $c =~ s/\$src/$s/;
	    $c =~ s/\$obj/$o/;
	    $text .= "\n\t$c";
	}
	$text .= "\n\n";
    }
    chop $text;
}


#
# BuildMocSrc(files)
#
# Builds the moc source files from headers and sources.
#

sub BuildMocSrc {
    my($f) = @_;
    my(@v,$m,$o);
    @v = split(/\s+/,$f);
    foreach $m ( @v ) {
	$o = $moc_output{$m};
	if ( defined($o) && ($o ne "") ) {
	    $text .= "$o: $m\n\t$moc_cmd $m -o $o\n\n";
	}
    }
    chop $text;
}

#
# BuildUicSrc(files)
#
# Builds the uic source files from interface definitions
#

sub BuildUicSrc {
    my($f) = @_;
    my(@v,$m,$o);
    @v = split(/\s+/,$f);
    foreach $m ( @v ) {
	$decl = $m;
	$decl =~ s/\.ui$/\.$uic_decl/;
	$impl = $m;
	$impl =~ s/\.ui$/\.$uic_impl/;
	$text .= "$decl: $m\n\t$uic_cmd $m -o $uic_decl_path/$decl\n\n";
	$fulldecl = $decl;
	$decl =~ s/.*\///; # No path - use -I... to find it.
	if ( ! ( $fulldecl eq $decl ) ) {
	  $text .= "$decl: $m\n\t$uic_cmd $m -o $decl\n\n";
	}
	$text .= "$impl: $m\n\t$uic_cmd $m -i $decl -o $impl\n\n";
    }
    chop $text;
}


#
# AddIncludePath(path)
#
# Adds path to the current include path, $project{"INCLUDEPATH"}.
#

sub AddIncludePath {
    my($path) = @_;
    my($p);
    if ( $project{"INCPATH"} &&
	 ($project{"INCPATH"} =~ /(?:^|\s)\Q$path\E(?:\s|$)/) ) {
	return;
    }
    $project{"INCLUDEPATH"} = "" if !defined($project{"INCLUDEPATH"});
    if ( !defined($project{"INCPATH_SEP"}) ) {
	if ( $project{"INCLUDEPATH"} =~ /;/ ) {
	    $project{"INCPATH_SEP"} = ";";
	} else {
	    $project{"INCPATH_SEP"} = " ";
	}
    }
    $p = $project{"INCLUDEPATH"};
    $p = ($p && $path) ? ($p . ";" . $path) : ($p . $path);
    $project{"INCLUDEPATH"} = $p;
    $p = join($project{"INCPATH_SEP"},&split_path($p));
    $p =~ s=[\\/]($project{"INCPATH_SEP"}|$)=$project{"INCPATH_SEP"}=g;
    $project{"INCPATH"} = $p;
}


#
# FindHighestLibVersion(dir,name)
#
# Returns the newest library version. Scans all the files in the specifies
# directory and returns the highest version number.
#
# Used on Windows only.
#
# Example:
#    FindHighestLibVersion("c:\qt\lib","qt") returns "200" if
#    the c:\qt\lib directory contains qt141.lib and qt200.lib.
#

sub FindHighestLibVersion {
    my($dir,$name) = @_;
    my(@files,$f,$v,$highest);
    $highest = "";
    @files = find_files($dir,"${name}.*\.lib");
    for $f ( @files ) {
	if ( $f =~ /(\d+)\.lib$/i ) {
	    $v = $1;
	    if ( $highest eq "" || $v > $highest ) {
		$highest = $v;
	    }
	} elsif ( $f =~ /(-mt(?:\d+)nc)\.lib$/i ) {
	    $v = $1;
	    if ( $highest eq "" || $v gt $highest ) {
		$highest = $v;
	    }
	}
    }
    return $highest;
}


#
# Finds files.
#
# Examples:
#   find_files("/usr","\.cpp$",1)   - finds .cpp files in /usr and below
#   find_files("/tmp","^#",0)	    - finds #* files in /tmp
#

sub find_files {
    my($dir,$match,$descend) = @_;
    my($file,$p,@files);
    local(*D);
    $dir =~ s=\\=/=g;
    ($dir eq "") && ($dir = ".");
    if ( opendir(D,&fix_path($dir)) ) {
	if ( $dir eq "." ) {
	    $dir = "";
	} else {
	    ($dir =~ /\/$/) || ($dir .= "/");
	}
	foreach $file ( readdir(D) ) {
	    next if ( $file  =~ /^\.\.?$/ );
	    $p = $dir . $file;
	    if ( $is_unix ) {
		($file =~ /$match/) && (push @files, $p);
	    } else {
		($file =~ /$match/i) && (push @files, $p);
	    }
	    if ( $descend && -d $p && ! -l $p ) {
		push @files, &find_files($p,$match,$descend);
	    }
	}
	closedir(D);
    }
    return @files;
}


#
# make_depend(file)
#
# Returns a list of included files.
# Uses the global $depend_path variable.
#

sub make_depend {
    my($file) = @_;
    my($i,$count);
    if ( $nodepend ) {
	return "";
    }
    if ( ! $depend_path_fixed ) {
	$depend_path_fixed = 1;
	if ( defined($project{"DEPENDPATH"}) ) {
	    $depend_path = $project{"DEPENDPATH"};
	} else {
	    $depend_path = "";
	}
	$count = 0;
	while ( $count < 100 ) {
	    if ( $depend_path =~ s/(\$[\{\(]?\w+[\}\)]?)/035/ ) {
		$_ = $1;
		s/[\$\{\}\(\)]//g;
		$depend_path =~ s/035/$ENV{$_}/g;
	    } else {
		$count = 100;
	    }
	}
	@dep_path = &split_path($depend_path);
    }
    @cur_dep_path = @dep_path;
    if ( $file =~ /(.*[\/\\])/ ) {
	$dep_curdir = $1;
	push @cur_dep_path, $dep_curdir;
    } else {
	$dep_curdir = "";
    }
    $dep_file = $file;
    &canonical_dep($file);
    %dep_dict = ();
    $i = &build_dep($file);
    chop $i;
    $i =~ s=/=$dir_sep=g unless $is_unix;
    $i =~ s=([a-zA-Z]):/=//$1/=g if (defined($gnuwin32) && $gnuwin32);
    return join(" ${linebreak}\n\t\t",split(/ /,$i) );
}

#
# build_dep() - Internal for make_depend()
#

sub build_dep {
    my($file) = @_;
    my(@i,$a,$n);
    $a = "";
    return $a if !(defined $depend_dict{$file});
    @i = split(/ /,$depend_dict{$file});
    for $n ( @i ) {
	if ( !defined($dep_dict{$n}) && ( defined($full_path{$n}) ) ) {
	    $dep_dict{$n} = 1;
	    $a .= $full_path{$n} . " " . &build_dep($n);
	}
    }
    return $a;
}

#
# canonical_dep(file) - Internal for make_depend()
#
# Reads the file and all included files recursively.
# %depend_dict associates a file name to a list of included files.
#

sub canonical_dep {
    my($file) = @_;
    my(@inc,$i);
    @inc = &scan_dep($file);
    if ( @inc ) {
	$depend_dict{$file} = join(" ",@inc);
	for $i ( @inc ) {
	    &canonical_dep($i) if !defined($depend_dict{$i});
	}
    }
}

#
# scan_dep(file) - Internal for make_depend()
#
# Returns an array of included files.
#

sub scan_dep {
    my($file) = @_;
    my($dir,$path,$found,@allincs,@includes,%incs);
    $path = $file;
    @includes = ();
    return @includes if $file =~ /\.$moc_ext$/; # avoid .moc files
    if ( ! (-f &fix_path($path)) ) {
	$found = 0;
	for $dir ( @cur_dep_path ) {
	    $path = $dir . $file;
	    last if ( $found = (-f &fix_path($path)) );
	}
	return @includes if ! $found;
    }
    undef $/;
    if ( open(TMP,&fix_path($path)) ) {
	$full_path{$file} = $path;
	$_ = <TMP>;
	s-//.*\n-\n-g;
	s-/\*.*?\*/--gs;			# strip C/C++ comments
	@allincs = split(/\n/,$_);
	@allincs = grep(/^\s*\#\s*include/,@allincs);
	foreach ( @allincs ) {			# all #include lines
	    next if !(/^\s*\#\s*include\s+[<\"]([^>\"]*)[>\"]/) || defined($incs{$1});
	    push(@includes,$1);
	    $incs{$1} = "1";
	    my $headerfile = $1;
	    my $hfile = $headerfile;
	    $hfile =~ s/\+/\\\+/g;
	    $full_path{$headerfile} ||= &fix_path( $headerfile )
	      if ( $project{"UICDECLS"} =~ /\b$hfile\b/ );
	}
	close(TMP);
    }
    $/ = "\n";
    return @includes;
}


#
# split_path(path)
#
# Splits a path containing : (Unix) or ; (MSDOS, NT etc.) separators.
# Returns an array.
#

sub split_path {
    my($p) = @_;
    my($s,@d);
    @d = ();
    return @d if !defined($p) || $p eq "";
    $p =~ s=:=;=g if $is_unix;
    $p =~ s=[/\\]+=/=g;
    if ( !($p =~ /;/) ) {
	$p =~ s/\s+/;/g;
    }
    $p =~ s/\s*;\s*/;/g;
    while( $p =~ /(?:(?:[^\"\;][^\;]*;*)|(?:\"[^\"]*\";*))/g ) {
	$s = $&;
	$s =~ s=\"==g;
	$s =~ s=[\s\;]+$==g;
	$s =~ s=([^/:])$=$1/=g;
	$s =~ s=/=$dir_sep=g unless $is_unix;
	push @d, $s;
    }
    return @d;
}


#
# fix_path(path)
#
# Converts all '\' to '/' if this really seems to be a Unix box.
# Also expands $(...) environment variables (god for $(QTDIR)).
#

sub fix_path {
    my($p) = @_;
    if ( $really_unix ) {
	$p =~ s-\\-/-g;
    } else {
	$p =~ s-/-\\-g;
    }
    $p =~ s/\$\(([^)]*)\)/$ENV{$1}/gs;
    return $p;
}


#
# mkdirp(filename,mode) - Internal for StdInit()
#
# Creates the directory specified by $filename, with permissions
# specified by mode (as modified by umask). Recursively calls
# mkdir, similar to 'mkdir -p'.
#

sub mkdirp {
    my($filename,$mode) = @_;
    if ( $filename =~ /\$\(\w+\)/ ) {  # ignore "$(something)"
	return 0;
    }
    $filename =~ s-[\\:/]+-/-g;
    if ( -d $filename ) {
	return 1;
    }
    $filename =~ m-^((.*)/)?(.*)-;
    if ( defined($2) && ! mkdirp($2,$mode) ) {
	return 0;
    }
    return mkdir($filename,$mode);
}
