#!@PERL@
use strict;
use Symbol;

my @modules;
my %module_dependency;
my %module_library_link_opts;
my %header_dependency;

$|=1;


# note: Mac OS X resource forks and .DS_Store files are explicity ignored

print "Box build environment setup.\n";

# Modules that everything else depends on, without being explicitly specified.
# Deprecated because we can't specify dependencies between them in the usual way,
# and it's totally unnecessary anyway:
my @implicit_deps;

# work out platform variables
use lib 'infrastructure';
use BoxPlatform;

print "Building on '$build_os $build_os_ver' using ".
	($bsd_make ? "BSD" : "GNU")." $make_command.\n\n";

# keep copy of command line args
my $makebuildenv_args = join(' ',@ARGV);

# make sure local files directory exists
unless(-d 'local')
{
	mkdir 'local',0755;
}


# flags about the environment
my %env_flags;

# print "Flag: $_\n" for(keys %env_flags);

# seed autogen code
print "Seeding autogen code...\n";
open FINDAUTOGEN,"find . -follow -name Makefile.extra |" or die "Can't use find for locating files";
while(<FINDAUTOGEN>)
{
	chomp;
	my $file = $_;
	$file =~ m~\A(.+)/[^/]+\Z~;
	my $dir = $1;
	open FL,$file or die "Can't open $_ for reading";
	my %vars;
	$vars{_PERL} = "@PERL@";
	my $do_cmds = 0;
	while(<FL>)
	{
		chomp;
		if(m/\A(.+)\s+=\s+(.+)\Z/)
		{
			# is a variable
			$vars{$1} = $2;
			next;
		}
		next unless m/\S/;
		if(m/AUTOGEN SEEDING/)
		{
			$do_cmds = 1;
		}
		elsif(m/\A\S/)
		{
			$do_cmds = 0 if $do_cmds == 2;
		}
		else
		{
			# command, run it?
			if($do_cmds)
			{
				$do_cmds = 2; # flag something has been done
				
				# subsitute variables, repeatedly
				my $c = $_;
				$c =~ s/\A\s+//;
				while(1)
				{
					my $did_subst = 0;
					
					for my $k (keys %vars)
					{
						$did_subst = 1 if $c =~ s/\$\($k\)/$vars{$k}/g;
					}
					
					last unless $did_subst;
				}
				
				# run command
				unless (0 == system("(cd $dir; $c)"))
				{
					die "Couldn't run command $c " .
						"(in $dir) for $file";
				}
			}
		}
	}
	close FL;
}
close FINDAUTOGEN;
print "done\n\n";


# open test mail program template file
my $test_template_file = 'infrastructure/buildenv-testmain-template.cpp';
open FL,$test_template_file or die "Can't open test template file\n";
my $test_template;
read FL,$test_template,-s $test_template_file;
close FL;


# extra platform defines
my $extra_platform_defines = '';

# read in module definitions file, and any files it includes
my @modules_files;
sub read_modules_file
{
	my ($mf) = @_;
	my $f = gensym;
	open $f,$mf or die "Can't open modules file '$mf'\n";
	while(<$f>)
	{
		if(m/\AINCLUDE\s+(\S+)\Z/)
		{
			# include another file
			read_modules_file($1)
		}
		else
		{
			push @modules_files,$_
		}
	}
	close $f;
}
read_modules_file('modules.txt');

# prepare directories...
mkdir "release",0755;
mkdir "debug",0755;

# is the library code in another directory?
my $external_lib = readlink('lib');
if($external_lib ne '')
{
	# adjust to root of the library distribution
	$external_lib =~ s!/lib\Z!!;
	$external_lib = '../'.$external_lib;
	# make symlinks
	make_obj_symlink('debug');
	make_obj_symlink('release');
}
sub make_obj_symlink
{
	my $m = $_[0];
	my $target = $external_lib."/$m/lib/";
	my $link = "$m/lib";
	# check link
	if(-e $link)
	{
		if(-l $link)
		{
			if(readlink($link) ne $target)
			{
				print "Warning: replacing $link with new link to $target\n";
				unlink $link;
			}
		}
		else
		{
			die "$link already exists, but it isn't a symbolic link"
		}
	}
	if(!-e $link)
	{
		symlink $target,$link or die "Can't make $m/lib symlink";
	}
}

print "Scanning code...\n";

my $modules_omitted = 0;
my $modules_omitting = 0;

# process lines in flattened modules files
for(@modules_files)
{
	# clean up line
	chomp; s/\A\s+//; s/#.*\Z//; s/\s+\Z//; s/\s+/ /g;
	next unless m/\S/;
	
	# omit bits on some platforms?
	if(m/\AEND-OMIT/)
	{
		$modules_omitting = 0;
		next;
	}

	next if $modules_omitting;

	if(m/\AOMIT:(.+)/)
	{
		if($1 eq $build_os or $1 eq $ac_target_os)
		{
			$modules_omitted = 1;
			$modules_omitting = 1;
		}
		next;
	}
	
	# split up...
	my ($mod, @deps_i) = split / /;
	
	# ignore this module?
	next if ignore_module($mod);
	
	# deps for this platform
	my @deps;
	for(@deps_i)
	{
		my ($dep,$exclude_from) = split /!/;
		# generic library translation
		$dep = $env_flags{'LIBTRANS_'.$dep} if exists($env_flags{'LIBTRANS_'.$dep});
		next if $dep eq '';
		if($exclude_from =~ m/\A\+(.+)\Z/)
		{
			$exclude_from = $1;
			my $inc = 0;
			for(split /,/,$exclude_from)
			{
				$inc = 1 if $_ eq $build_os
			}
			push @deps,$dep if $inc
		}
		else
		{
			my $inc = 1;
			for(split /,/,$exclude_from)
			{
				$inc = 0 if $_ eq $build_os
			}
			push @deps,$dep if $inc
		}
	}
	
	# check directory exists
	die "Module $mod can't be found\n" unless -d $mod;
	
	# and put in lists
	push @modules,$mod;
	my @md;	# module dependencies
	my @lo; # link line options
	for (@deps)
	{
		if(/\A-l/)
		{
			push @lo,$_
		}
		else
		{
			push @md,$_ unless ignore_module($_)
		}
	}
	$module_dependency{$mod} = [@implicit_deps,@md];
	$module_library_link_opts{$mod} = [@lo];
	
	# make directories, but not if we're using an external library and this a library module
	my ($s,$d) = split /\//,$mod;
	if ($s ne 'lib' or $external_lib eq '')
	{
		mkdir "release/$s",0755;
		mkdir "release/$s/$d",0755;
		mkdir "debug/$s",0755;
		mkdir "debug/$s/$d",0755;
	}
}

# make dirs for implicit dep
foreach my $dep (@implicit_deps)
{
	mkdir "release/$dep",0755;
	mkdir "debug/$dep",0755;
}

# write a list of all the modules we've configured to use
open CONFIGURED_MODS,'>local/modules.h' or die "Can't write configured modules list";
print CONFIGURED_MODS <<__E;
// automatically generated file, do not edit
#ifndef _CONFIGURED_MODULES__H
#define _CONFIGURED_MODULES__H
__E
for(@implicit_deps,@modules)
{
	my $m = $_;
	$m =~ s~/~_~;
	print CONFIGURED_MODS "#define MODULE_$m\n";
}
print CONFIGURED_MODS <<__E;
#endif // _CONFIGURED_MODULES__H
__E
close CONFIGURED_MODS;


# now make a list of all the .h files we can find, recording which module they're in
my %hfiles;
for my $mod (@modules, @implicit_deps)
{
	opendir DIR,$mod;
	my @items = readdir DIR;
	closedir DIR;

	# add in items from autogen directories, and create output directories
	{
		my @autogen_items;
		
		for my $di (@items)
		{
			if($di =~ m/\Aautogen/ && -d "$mod/$di")
			{
				# Read items
				my $d = "$mod/$di";
				opendir DIR,$d;
				my @i = readdir DIR;
				closedir DIR;
				for(@i)
				{
					next if m/\A\./;
					push @autogen_items,"$di/$_"
				}
			}
		}
		@items = (@items, @autogen_items);
	}

	for(grep /\.h\Z/i, @items)
	{
		next if /\A\._/;	# Temp Mac OS Resource hack
		die "Header file $_ already used in module ".$hfiles{$_}.
			", cannot add to $mod\n" if exists $hfiles{$_};
		$hfiles{$_} = $mod
	}
}

for my $mod (@modules, @implicit_deps)
{
	opendir DIR,$mod;
	for my $h (grep /\.h\Z/i, readdir DIR)
	{
		next if $h =~ /\A\./;		# Ignore Mac resource forks, autosaves, etc

		open FL,"$mod/$h" or die "can't open $mod/$h";
		my $f;
		read FL,$f,-s "$mod/$h";
		close FL;

		while($f =~ m/\#include\s+"([^"]+?)"/g)
		{
			my $i = $1;
			# ignore autogen exceptions
			next if $i =~ m/\Aautogen_.+?Exception.h\Z/;
			# record dependency
			${$header_dependency{$h}}{$i} = 1 if exists $hfiles{$i};
		}
	}
	closedir DIR;
}

my $default_cflags = '@CFLAGS@';
my $default_cxxflags = '@CXXFLAGS@';
$default_cflags =~ s/ -O2//g;
$default_cxxflags =~ s/ -O2//g;
my $debug_base_dir = 'debug';
my $release_base_dir = 'release';
my $debugger = '@with_debugger@';

my $release_flags = "-O2";
if ($target_windows)
{
	$release_flags = "-O0 -g";
}

# Then write a master Makefile, and a mini-Makefile for each module
print "done\n\nGenerating Makefiles...\n";

my $makefile_ifdef_prefix = $bsd_make ? "." : "";
my $autoconf_cppflags = '@CPPFLAGS@';
my $autoconf_cxxflags = '@CXXFLAGS_STRICT@';
my $autoconf_ldflags  = '@LDFLAGS@';

open MASTER_MAKEFILE, ">Makefile" or die "Makefile: $!";
print MASTER_MAKEFILE <<__E;
#
# AUTOMATICALLY GENERATED FILE
#    do not edit!
#
#

# "parcels" is defined near the end of this Makefile.
default: parcels

CC = @CC@
CXX = @CXX@
AR = @AR@
RANLIB = @RANLIB@
PERL = @PERL@
WINDRES = @WINDRES@

# Work around a mistake in QDBM (using <angled> includes for a file not in the
# system path) by adding it to the include path with -I.
DEFAULT_CFLAGS = $autoconf_cppflags $default_cflags $autoconf_cxxflags \\
	$extra_platform_defines $platform_compile_line_extra \\
	-DBOX_VERSION="\\"$product_version\\"" -Iqdbm
DEFAULT_CXXFLAGS = $autoconf_cppflags $default_cxxflags $autoconf_cxxflags \\
	$extra_platform_defines $platform_compile_line_extra \\
	-DBOX_VERSION="\\"$product_version\\""
LDFLAGS += $autoconf_ldflags \@LDADD_RDYNAMIC@ $platform_link_line_extra

RELEASE_CFLAGS   = \$(DEFAULT_CFLAGS)   -DBOX_RELEASE_BUILD $release_flags
RELEASE_CXXFLAGS = \$(DEFAULT_CXXFLAGS) -DBOX_RELEASE_BUILD $release_flags
RELEASE_OUTBASE  = $release_base_dir
# http://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode_using.html#debug_mode.using.mode
DEBUG_CFLAGS   = \$(DEFAULT_CFLAGS)   -g -O0 -D_GLIBCXX_DEBUG
DEBUG_CXXFLAGS = \$(DEFAULT_CXXFLAGS) -g -O0 -D_GLIBCXX_DEBUG
DEBUG_OUTBASE  = $debug_base_dir

__E

if ($bsd_make)
{
	print MASTER_MAKEFILE <<__E;
.ifdef V
HIDE     = 
_CC      = \$(CC)
_CXX     = \$(CXX)
_LINK    = \$(CXX)
_WINDRES = \$(WINDRES)
_AR      = \$(AR)
_RANLIB  = \$(RANLIB)
_PERL    = \$(PERL)
.else
HIDE     = @
_CC      = @ echo "  [CC]     " \$(*F) && \$(CC)
_CXX     = @ echo "  [CXX]    " \$(*F) && \$(CXX)
_LINK    = @ echo "  [LINK]   " \$(*F) && \$(CXX)
_WINDRES = @ echo "  [WINDRES]" \$(*F) && \$(WINDRES)
_AR      = @ echo "  [AR]     " \$(*F) && \$(AR)
_RANLIB  = @ echo "  [RANLIB] " \$(*F) && \$(RANLIB)
_PERL    = @ echo "  [PERL]   " \$(*F) && \$(PERL) >/dev/null
.endif

__E
}
else
{
	print MASTER_MAKEFILE <<__E;
HIDE     = \$(if \$(V),,@)
_CC      = \$(if \$(V),\$(CC),      @ echo "  [CC]      \$<" && \$(CC))
_CXX     = \$(if \$(V),\$(CXX),     @ echo "  [CXX]     \$<" && \$(CXX))
_LINK    = \$(if \$(V),\$(CXX),     @ echo "  [LINK]    \$@" && \$(CXX))
_WINDRES = \$(if \$(V),\$(WINDRES), @ echo "  [WINDRES] \$<" && \$(WINDRES))
_AR      = \$(if \$(V),\$(AR),      @ echo "  [AR]      \$@" && \$(AR))
_RANLIB  = \$(if \$(V),\$(RANLIB),  @ echo "  [RANLIB]  \$@" && \$(RANLIB))
_PERL    = \$(if \$(V),\$(PERL),    @ echo "  [PERL]    \$@" && \$(PERL) >/dev/null)

__E
}

my %module_resources_win32;
my @debug_build_targets;
my @release_build_targets;
my @all_clean_targets;
my @all_realclean_targets;
my %mod_type_name;
my %mod_end_targets;
my %library_targets;

# First, loop over all modules and quickly categorise them and generate the target filenames.
# We need this to write correct dependency info into the makefile when modules are listed
# out of order in modules.txt, which isn't strictly necessary but may look tidier.
for my $mod (@implicit_deps, @modules)
{
	print $mod,"\n";
	
	my ($type,$name) = split /\//,$mod;
	if (not $name)
	{
		# External modules such as qdbm have no "type" in the directory path.
		$name = $mod;
		$type = 'lib';
	}

	# is target a library?
	my $target_is_library = ($type ne 'bin' && $type ne 'test');

	# make target name
	my $end_target_file = $name;

	if ($target_is_library)
	{
		$end_target_file .= '.a';
	}
	else
	{
		$end_target_file .= $platform_exe_ext;
	}

	$end_target_file = '_test'.$platform_exe_ext if $type eq 'test';

	$mod_type_name{$mod} = [$type, $name];
	$mod_end_targets{$mod} = $end_target_file;
	$library_targets{$mod} = $target_is_library;
}

# Now loop over them again, generating makefile instructions etc.
for my $mod (@implicit_deps, @modules)
{
	my ($type,$name) = @{$mod_type_name{$mod}};
	my $end_target_file = $mod_end_targets{$mod};
	my $target_is_library = $library_targets{$mod};

	# add additional files for tests
	if($type eq 'test')
	{
		my $testmain = $test_template;
		$testmain =~ s/TEST_NAME/$name/g;
		open TESTMAIN,">$mod/_main.cpp" or die "Can't open test main file for $mod for writing\n";
		print TESTMAIN $testmain;
		close TESTMAIN;
		
		# test file...
		sub writetestfile
		{
			my ($filename,$runcmd,$module) = @_;		

			open TESTFILE,">$filename" or die "Can't open " .
				"test script file for $module for writing\n";
			print TESTFILE "#!/bin/sh\necho TEST: $module\n";

			if ($target_windows)
			{
				print TESTFILE <<__E;
kill_process()
{
	if test -r testfiles/\$1.pid; then
		/bin/kill -0 -f `cat testfiles/\$1.pid` \\
		&& /bin/kill -f `cat testfiles/\$1.pid`
		rm testfiles/\$1.pid
	fi
}
__E
			}
			else
			{
				print TESTFILE <<__E;
kill_process()
{
	test -r testfiles/\$1.pid \\
	&& kill -0 `cat testfiles/\$1.pid` \\
	&& kill    `cat testfiles/\$1.pid`
}
__E
			}

			if (-d "$module/testfiles")
			{
				print TESTFILE <<__E;
kill_daemons()
{
	kill_process bbackupd
	kill_process bbstored
	kill_process httpserver
	kill_process s3simulator
}

echo Killing any running daemons...
kill_daemons
__E
			}

			print TESTFILE <<__E;
echo Removing old test files...
chmod -R a+rwx testfiles
rm -rf testfiles

echo Copying new test files...
cp -p -R ../../../$module/testfiles .

__E

			if (-e "$module/testextra")
			{
				open FL,"$module/testextra" or die 
					"Can't open $module/testextra";
				while(<FL>) {print TESTFILE}
				close FL;
			}

			print TESTFILE "$runcmd\nexit_status=\$?\n";

			if (-d "$module/testfiles")
			{
				print TESTFILE <<__E;
kill_daemons
__E
			}

			print TESTFILE "exit \$exit_status\n";
			close TESTFILE;
		}
		
		writetestfile("$mod/t", "GLIBCXX_FORCE_NEW=1 ".
			'./_test' . $platform_exe_ext . ' "$@"', $mod);

		if($debugger)
		{
			writetestfile("$mod/t-gdb", "GLIBCXX_FORCE_NEW=1 ".
				$debugger . ' ./_test' . $platform_exe_ext . ' "$@"', $mod);
		}
		else
		{
			writetestfile("$mod/t-gdb",
				"echo 'No debugger was detected by configure script'\n".
				"exit 2", $mod);
		}
	}
	
	my @all_deps_for_module;

	{
		# work out what dependencies need to be run
		my @deps_raw;
		sub add_mod_deps
		{
			my ($arr_r,$nm) = @_;
			if($#{$module_dependency{$nm}} >= 0)
			{
				push @$arr_r,@{$module_dependency{$nm}};
				for(@{$module_dependency{$nm}})
				{
					add_mod_deps($arr_r,$_)
				}
			}
		}
		add_mod_deps(\@deps_raw, $mod);
		# and then dedup and reorder them
		my %d_done;
		foreach my $dep (reverse @deps_raw)
		{
			if(!exists $d_done{$dep})
			{
				# insert
				push @all_deps_for_module, $dep;
				# mark as done
				$d_done{$dep} = 1;
			}
		}
	}	

	# get the list of library things to add -- in order of dependency 
	# so things link properly
	my @lib_files;
	my @dep_targets;
	my @include_search_dirs;

	foreach my $dep (reverse @all_deps_for_module)
	{
		my $dep_target = $mod_end_targets{$dep};
		die "No output file found for $dep" unless $dep_target;
		$dep_target = "$dep/$dep_target";
		push @dep_targets, $dep_target;

		if ($dep =~ m|^lib\/(.+)$|)
		{
			push @lib_files, $dep_target;
			push @include_search_dirs, $dep;
		}
		elsif ($dep =~ m|^([^/]+)$|)
		{
			push @lib_files, $dep_target;
			push @include_search_dirs, $dep;
		}
	}

	# make include path
	my $cpp_include_paths = join(' ',map {"-I$_"} @include_search_dirs);
	print MASTER_MAKEFILE "${type}_${name}_includes = $cpp_include_paths\n";

	# adjust for outdir
	my $debug_end_target   = "\$(DEBUG_OUTBASE)/$mod/$end_target_file";
	my $release_end_target = "\$(RELEASE_OUTBASE)/$mod/$end_target_file";
	push @debug_build_targets,   $debug_end_target;
	push @release_build_targets, $release_end_target;

	# start the makefile
	open MINI_MODULE_MAKEFILE,">$mod/Makefile" or die "Can't open Makefile for $mod\n";
	print MINI_MODULE_MAKEFILE <<__E;
#
# AUTOMATICALLY GENERATED FILE
#    do not edit!
#
${makefile_ifdef_prefix}ifdef RELEASE
TARGET = $release_base_dir/$mod/$end_target_file
${makefile_ifdef_prefix}else
TARGET = $debug_base_dir/$mod/$end_target_file
${makefile_ifdef_prefix}endif

.PHONY: default
default:
	\$(MAKE) -C ../.. \$(TARGET)

.PHONY: clean
clean:
	\$(MAKE) -C ../.. clean_${type}_${name}

.PHONY: realclean
realclean:
	\$(MAKE) -C ../.. realclean_${type}_${name}
__E
	close MINI_MODULE_MAKEFILE;

	opendir DIR, $mod;
	my @items = readdir DIR;
	closedir DIR;
	
	# add in items from autogen directories, and create output directories
	{
		my @autogen_items;
		for my $di (@items)
		{
			if($di =~ m/\Aautogen/ && -d "$mod/$di")
			{
				# Read items
				my $d = "$mod/$di";
				opendir DIR, $d;
				my @i = readdir DIR;
				closedir DIR;
				for (@i)
				{
					next if m/\A\./;
					push @autogen_items,"$di/$_"
				}
				
				# output directories
				mkdir "release/$mod/$di",0755;
				mkdir "debug/$mod/$di",0755;
			}
		}
		@items = (@items, @autogen_items);
	}
	
	# first, obtain a list of dependencies within the .h files
	my %headers;
	for my $h (grep /\.h\Z/i, @items)
	{
		open FL,"$mod/$h";
		my $f;
		read FL,$f,-s "$mod/$h";
		close FL;

		while($f =~ m/\#include\s+"([^"]+?)"/g)
		{
			${$headers{$h}}{$1} = 1 if exists $hfiles{$1};
		}
	}
	
	# write the recipes for debug and release builds of each file
	foreach my $var_prefix ('DEBUG', 'RELEASE')
	{
		my $make;

		# then... do the cpp files...
		my @obj_base;
		for my $file (@items)
		{
			my $is_c = $file =~ m/\A(.+)\.c\Z/i;
			my $is_cpp = $file =~ m/\A(.+)\.cpp\Z/i;
			my $is_rc  = $file =~ m/\A(.+)\.rc\Z/i;
			my $base = $1;

			# Don't try to compile .rc files except on Windows:
			next if not $is_c and not $is_cpp and not ($is_rc and $target_windows);
			next if $file =~ /\A\._/; # Temp Mac OS Resource hack

			# store for later
			push @obj_base, $base;
		
			# get the file...
			open FL, "$mod/$file";
			my $f;
			read FL, $f, -s "$mod/$file";
			close FL;
			
			my %dep;

			while($f =~ m/\#include\s+"([^"]+?)"/g)
			{
				insert_dep($1, \%dep) if exists $hfiles{$1};
			}
			
			# output filename
			my $out_name = "\$(${var_prefix}_OUTBASE)/$mod/$base.o";
			
			# write the line for this cpp file
			my @dep_paths = map 
			{ 
				($hfiles{$_} eq $mod)
				? "$mod/$_" 
				: $hfiles{$_}."/$_"
			}
			keys %dep;

			$make .= "$out_name: $mod/$file @dep_paths\n";

			if ($is_c)
			{
				$make .= "\t\$(_CC) \$(${var_prefix}_CFLAGS) ".
					"\$(${type}_${name}_includes) -DBOX_MODULE=\"\\\"$mod\\\"\" " .
					"-c $mod/$file -o $out_name\n\n";
			}
			if ($is_cpp)
			{
				$make .= "\t\$(_CXX) \$(${var_prefix}_CXXFLAGS) ".
					"\$(${type}_${name}_includes) -DBOX_MODULE=\"\\\"$mod\\\"\" " .
					"-c $mod/$file -o $out_name\n\n";
			}
			elsif ($is_rc)
			{
				$make .= "\t\$(_WINDRES) $file $out_name\n\n";
				my $res_list = $module_resources_win32{$mod};
				$res_list ||= [];
				push @$res_list, $base.'.o';
				$module_resources_win32{$mod} = $res_list;
			}
		}

		# need to see if the extra makefile fragments require extra object files
		# or include any more makefiles
		my @objs = @obj_base;
		my @makefile_includes;
		
		additional_objects_from_make_fragment("$mod/Makefile.extra", \@objs, \@makefile_includes);
		additional_objects_from_make_fragment("$mod/Makefile.extra.$build_os", \@objs, \@makefile_includes);

		my $prefixed_end_target = "\$(${var_prefix}_OUTBASE)/$mod/$end_target_file";
		my $o_file_list = join(' ',map {"\$(${var_prefix}_OUTBASE)/$mod/$_.o"} sort @objs);
		my @prefixed_lib_files = map {"\$(${var_prefix}_OUTBASE)/$_"} @lib_files;
		my @prefixed_dep_targets = map {"\$(${var_prefix}_OUTBASE)/$_"} @dep_targets;

		print MASTER_MAKEFILE "$prefixed_end_target: $o_file_list";
		print MASTER_MAKEFILE " @prefixed_dep_targets" unless $target_is_library;
		print MASTER_MAKEFILE "\n";
		
		if ($target_windows)
		{
			foreach my $dep (@all_deps_for_module)
			{
				my $res_list = $module_resources_win32{$dep};
				next unless $res_list;
				$o_file_list .= ' '.join(' ', 
					map {"\$(${var_prefix}_OUTBASE)/$dep/$_"} @$res_list);
			}
		}

		# stuff to make the final target...
		if($target_is_library)
		{
			# make a library archive...
			print MASTER_MAKEFILE "\t\$(HIDE) (rm -f $prefixed_end_target)\n";
			print MASTER_MAKEFILE "\t\$(_AR) cq $prefixed_end_target $o_file_list\n";
			print MASTER_MAKEFILE "\t\$(_RANLIB) $prefixed_end_target\n";
		}
		else
		{
			# work out library options
			# need to be... least used first, in absolute order they appear in the modules.txt file
			my @libops;
			
			sub libops_fill
			{
				my ($module, $libops_ref) = @_;
				
				my $library_link_opts = $module_library_link_opts{$module};
				if ($library_link_opts)
				{
					push @$libops_ref, @$library_link_opts;
				}
				
				my $deps = $module_dependency{$module};
				foreach my $dep (@$deps)
				{
					libops_fill($dep, $libops_ref);
				}
			}
			
			libops_fill($mod,\@libops);
			
			my $lo = '';
			my %ldone;
			for(@libops)
			{
				next if exists $ldone{$_};
				$lo .= ' '.$_;
				$ldone{$_} = 1;
			}
		
			# link line...
			print MASTER_MAKEFILE "\t\$(_LINK) \$(LDFLAGS) " .
				"-o $prefixed_end_target $o_file_list " .
				"@prefixed_lib_files $lo $platform_lib_files\n";
		}

		# tests need to copy the test file over
		if($type eq 'test')
		{
			print MASTER_MAKEFILE <<__E;
	\$(HIDE) cp $mod/t \$(${var_prefix}_OUTBASE)/$mod/t
	\$(HIDE) chmod u+x \$(${var_prefix}_OUTBASE)/$mod/t
	\$(HIDE) cp $mod/t-gdb \$(${var_prefix}_OUTBASE)/$mod/t-gdb
	\$(HIDE) chmod u+x \$(${var_prefix}_OUTBASE)/$mod/t-gdb
__E
		}

		print MASTER_MAKEFILE $make,"\n";

		for(@makefile_includes)
		{
			print MASTER_MAKEFILE "\n.include <$_>\n";
		}
}

	print MASTER_MAKEFILE <<__E;
clean_${type}_${name}:
	rm -rf \$(DEBUG_OUTBASE)/$mod/*
	rm -rf \$(RELEASE_OUTBASE)/$mod/*

realclean_${type}_${name}: clean_${type}_${name}
	rm -f $mod/t $mod/t-gdb $mod/Makefile
	find $mod -name 'autogen_*' -type f -exec rm -f {} \\;
__E
	push @all_clean_targets, "clean_${type}_${name}";
	push @all_realclean_targets, "realclean_${type}_${name}";

	my $includes = "";
	
	if(-e "$mod/Makefile.extra")
	{
		$includes .= ".include <$mod/Makefile.extra>\n\n";
	}
	if(-e "$mod/Makefile.extra.$build_os")
	{
		$includes .= ".include <$mod/Makefile.extra.$build_os>\n\n";
	}

	if(!$bsd_make)
	{
		# need to post process this into a GNU makefile
		$includes =~ s/\A\.\s*(ifdef|else|endif|ifndef)/$1/;
		$includes =~ s/\A\.\s*include\s+<(.+?)>/include $1/;
		$includes =~ s/-D\s+(\w+)/$1=1/g;
	}

	print MASTER_MAKEFILE $includes;
}

my @parcels;
my %parcel_contents;

sub starts_with ($$)
{
	my ($string,$expected) = @_;
	return substr($string, 0, length $expected) eq $expected;
}

sub os_matches ($)
{
	my ($prefix_string) = @_;
	my @prefixes = split m'\,', $prefix_string;
	foreach my $prefix (@prefixes)
	{
		return 1 if starts_with($build_os,  $prefix);
		return 1 if starts_with($ac_target_os, $prefix);
		return 1 if starts_with("$ac_target_cpu-$ac_target_os",
			$prefix);
		return 1 if starts_with($ac_target, $prefix);
	}
	return 0;
}

my $copy_command = "cp -p";

open PARCELS,"parcels.txt" or die "Can't open parcels file";
{
	my $cur_parcel = '';
	while(<PARCELS>)
	{
		chomp; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g;
		next unless m/\S/;
		
		# omit bits on some platforms?
		next if m/\AEND-OMIT/;
		if(m/\AOMIT:(.+)/)
		{
			if (os_matches($1))
			{
				while(<PARCELS>)
				{
					last if m/\AEND-OMIT/;	
				}
			}
			next;
		}

		if (m'\AONLY:(.+)')
		{
			if (not os_matches($1))
			{
				while (<PARCELS>)
				{
					last if m'\AEND-ONLY';
				}
			}
			next;
		}
		next if (m'\AEND-ONLY');

		if (m'\AEXCEPT:(.+)')
		{
			if (os_matches($1))
			{
				while (<PARCELS>)
				{
					last if m'\AEND-EXCEPT';
				}
			}
			next;
		}
		next if (m'\AEND-EXCEPT');
		
		# new parcel, or a new parcel definition?
		if(m/\A\s+(.+)\Z/)
		{
			push @{$parcel_contents{$cur_parcel}},$1
		}
		else
		{
			$cur_parcel = $_;
			push @parcels,$_;
		}
	}
}
close PARCELS;

# create parcels directory
mkdir "parcels",0755;
mkdir "parcels/scripts",0755;

print MASTER_MAKEFILE "parcels:\t",join(' ',map {"build-".$_} @parcels),"\n\n";

my $runtest_script = $target_windows ? './infrastructure/mingw/runtest.sh' 
	: './runtest.pl';

print MASTER_MAKEFILE <<__END_OF_FRAGMENT;
.PHONY: test
test:	debug_tests release_tests

.PHONY: debug_tests
debug_tests:
	$runtest_script ALL debug

.PHONY: release_tests
release_tests:
	$runtest_script ALL release

.PHONY: docs
docs:
	cd docs; \$(MAKE)

__END_OF_FRAGMENT

my $release_flag = BoxPlatform::make_flag('RELEASE');
my @parcel_targets;

for my $parcel (@parcels)
{
	my $version = BoxPlatform::parcel_root($parcel);
	my $make_target = BoxPlatform::parcel_target($parcel);
	my $dir = BoxPlatform::parcel_dir($parcel);
	my @parcel_deps;

	# Need to use BSD install on Solaris
	my $install_bin = $build_os eq 'SunOS' ? '/usr/ucb/install' : 'install';

	unless ($target_windows)
	{
		open SCRIPT,">parcels/scripts/install-$parcel" or die
			"Can't open installer script for $parcel for writing";
		print SCRIPT "#!/bin/sh\n\n";
	}

	for(@{$parcel_contents{$parcel}})
	{
		my @args = split /\s+/;

		my ($type,$name,$dest) = @args;
		my $optional = 0;
		my $install  = 1;

		if ($type eq 'optional')
		{
			$optional = 1;
			shift @args;
			($type,$name,$dest) = @args;
		}

		if ($type eq 'noinstall')
		{
			$install = 0;
			shift @args;
			($type,$name,$dest) = @args;
		}

		if($type eq 'bin')
		{
			my $exeext = $platform_exe_ext;
			print MASTER_MAKEFILE <<EOF;
$dir/$name$exeext: release/bin/$name/$name$exeext
	mkdir -p $dir
	$copy_command release/bin/$name/$name$exeext $dir

EOF
			push @parcel_deps, "$dir/$name$exeext";
		}
		elsif ($type eq 'script')
		{
			# Replace any variables ($ac_target etc.) with their
			# values.
			$name =~ s|(\$[^/ ]+)|$1|eeg;
			my $fullpath = $name;
			my $filename = $name;
			# remove path from script name
			$filename =~ s{.*/}{};

			print MASTER_MAKEFILE <<EOF;
$dir/$filename: $fullpath
	mkdir -p $dir
EOF

			if ($optional)
			{
				print MASTER_MAKEFILE "\ttest -r $fullpath " .
					"&& $copy_command $fullpath $dir || true\n";
			}
			else
			{
				print MASTER_MAKEFILE "\t$copy_command $fullpath $dir\n";
			}

			print MASTER_MAKEFILE "\n";

			push @parcel_deps, "$dir/$filename";
		}
		elsif($type eq 'man')
		{
			print MASTER_MAKEFILE <<EOF;
$dir/${name}.gz: docs/man/${name}.gz
	mkdir -p $dir
	$copy_command docs/man/${name}.gz $dir

EOF
			# Releases have the docs pre-made, but users
			# may want to rebuild them for some reason.
			my $docbook_source = "docs/docbook/${name}";
			$docbook_source =~ s/\.[58]$/.xml/;
			print MASTER_MAKEFILE <<EOF;
docs/man/${name}.gz: $docbook_source docs/docbook/bb-man.xsl
	cd docs; \$(MAKE) man/${name}.gz

EOF
			push @parcel_deps, "$dir/${name}.gz";
		}
		elsif($type eq 'html')
		{
			print MASTER_MAKEFILE <<EOF;
$dir/docs/${name}.html: docs/htmlguide/man-html/${name}.html
	mkdir -p $dir/docs
	$copy_command docs/htmlguide/man-html/${name}.html $dir/docs

EOF
			# Releases have the docs pre-made, but users
			# may want to rebuild them for some reason.
			my $docbook_source = "docs/docbook/${name}.xml";
			print MASTER_MAKEFILE <<EOF;
docs/htmlguide/man-html/${name}.html: $docbook_source docs/docbook/bb-nochunk-book.xsl
	cd docs; \$(MAKE) htmlguide/man-html/${name}.html

EOF
			push @parcel_deps, "$dir/docs/${name}.html";
		}
		elsif ($type eq 'subdir')
		{
			push @parcel_deps, "build_${type}_${name}";
		}
	}

	print MASTER_MAKEFILE <<EOF;
build-$parcel:	$make_target

$make_target: @parcel_deps
	test -d $dir || mkdir $dir
EOF

	push @parcel_targets, "build-$parcel";
	
	for(@{$parcel_contents{$parcel}})
	{
		my @args = split /\s+/;

		my ($type,$name,$dest) = @args;

		my $optional = 0;
		my $install  = 1;

		if ($type eq 'optional')
		{
			$optional = 1;
			shift @args;
			($type,$name,$dest) = @args;
		}

		if ($type eq 'noinstall')
		{
			$install = 0;
			shift @args;
			($type,$name,$dest) = @args;
		}

		if ($type eq 'script')
		{
			# remove path from script name
			$name =~ s{.*/}{};
		}

		if ($type eq 'html')
		{
			$dest = "share/doc/@PACKAGE_TARNAME@";
			$name = "docs/$name.html";
		}

		if ($type eq 'man')
		{
			$name =~ /([0-9])$/;
			$dest = "man/man$1";
			$name =~ s/$/\.gz/;
		}

		if ($install and not $target_windows and not $type eq "subdir")
		{
			my $local_install_dir = $install_into_dir;
			if (defined $dest)
			{
				if ($dest =~ m,^/,)
				{
					# Don't add $prefix if $dest is a literal path
					$local_install_dir = $dest;
				}
				else
				{
					$local_install_dir = "@prefix@/$dest";
				}
			}
			print SCRIPT "mkdir -p " .
				"\${DESTDIR}$local_install_dir/\n";
			print SCRIPT "$install_bin $name " .
				"\${DESTDIR}$local_install_dir\n";
		}
	}

	unless ($target_windows)
	{	
		close SCRIPT;
		chmod 0755,"parcels/scripts/install-$parcel";
	}
	
	my $root = BoxPlatform::parcel_root($parcel);

	unless ($target_windows)
	{
		print MASTER_MAKEFILE "\tcp parcels/scripts/install-$parcel $dir\n";
	}

	print MASTER_MAKEFILE "\t(cd parcels; tar cf - $root | gzip -9 - > $root.tgz )\n";
	
	print MASTER_MAKEFILE "\n";

	unless ($target_windows)
	{	
		print MASTER_MAKEFILE "install-$parcel:\n";
		print MASTER_MAKEFILE "\t(cd $dir; ./install-$parcel)\n\n";
	}
}

print MASTER_MAKEFILE <<EOF;

debug_build:   @debug_build_targets
release_build: @release_build_targets
parcels:       @parcel_targets

# well-known targets that users might expect to be able to build:
install:
	cat local/install.msg

clean:         @all_clean_targets
	cd docs; \$(MAKE) clean

realclean: clean @all_realclean_targets
	find release debug       -type f -exec rm -f {} \\;

# and finally a target for rebuilding the build system:
buildsystem:
	perl ./infrastructure/makebuildenv.pl $makebuildenv_args
EOF

for my $parcel (@parcels)
{
	# need to use -f to avoid error if they don't exist (already cleaned)
	print MASTER_MAKEFILE "\trm -rf ", BoxPlatform::parcel_dir($parcel),    "\n";
	print MASTER_MAKEFILE "\trm -f  ", BoxPlatform::parcel_target($parcel), "\n";
}

open INSTALLMSG,">local/install.msg" or die "Can't open install message file for writing";
print INSTALLMSG <<__E;

Parcels need to be installed separately, and as root. Type one of the following:

__E

for(@parcels)
{
	print INSTALLMSG "    $make_command install-".$_."\n";
}
print INSTALLMSG "\n";

close INSTALLMSG;

close MASTER_MAKEFILE;

print "\nType 'cd <module_dir>; $make_command' to build a module\n\n";

if($modules_omitted)
{
	print "\nNOTE: Some modules have been omitted on this platform\n\n"
}

sub insert_dep
{
	my ($h,$dep_r) = @_;
	
	# stop random recusion
	return if exists $$dep_r{$h};
	
	# insert more depencies
	insert_dep($_,$dep_r) for keys %{$header_dependency{$h}};

	# mark this one as a dependency
	$$dep_r{$h} = 1;
}


sub additional_objects_from_make_fragment
{
	my ($fn,$objs_r,$include_r) = @_;
	
	if(-e $fn)
	{
		open FL,$fn or die "Can't open $fn";
		
		while(<FL>)
		{
			chomp;
			if(m/link-extra:\s*(.+)\Z/)
			{
				my $extra = $1;
				do
				{
					my @o = split /\s+/, $extra;
					for(@o)
					{
						push @$objs_r,$1 if m/\A(.+)\.o\Z/;
					}
					last unless $extra =~ m'\\$';
					$extra = <FL>;
				}
				while(1);
			}
			elsif(m/include-makefile:\s*(\S+)/)
			{
				push @$include_r,$1
			}
		}
		
		close FL;
	}
}


sub ignore_module
{
	exists $env_flags{'IGNORE_'.$_[0]}
}
