#!/usr/bin/perl

use Cwd;

# Authors: William Lott, Nick Kramer
# rcs-header: $Header: /home/cvsroot/gd/src/tools/shared-misc/gen-makefile.in,v 1.33 2002/02/26 00:48:55 brent Exp $

# Usage: gen-makefile [-pplatforms.descr] directory
#
# gen-makefile turns a single Makegen file into a Makefile.
# gen-makefile does the bulk of the work for mk-build-tree, which
# calls gen-makefile once on each Makegen in the tree.  The main
# reason we split gen-makefile from mk-build-tree is that gen-makefile
# uses eval, so we need a fresh environment for each Makegen file to
# keep them from accidentally pissing on each other.
#
# All Defaults files must define the following variables: $target_name
# and $srcroot.  Many Defaults files also define $destdir and $D2C.
# (See below for a full description of all available variables)
#
# gen-makefile provides a variety of functions which Makegen files can
# call.  These functions include:
#
#    compile_subdirs(@subdirs)
#    emit_library_rule($lidfile, $extradeps, $extraflags, @keywords)
#    emit_c_file_rule($base_file_name, @header_files)
#    emit_cc_file_rule($base_file_name, @header_files)
#    emit_melange_rule($base_file_name, @dependencies)
#    emit_parsergen_rule($base_file_name)
#    install($dest_subdir, @files)
#    install_from_src($dest_subdir, @files)
#    install_executable_from_src($dest_subdir, @files)
#    emit_mindycomp_rule($dylan_source)
#    emit_dbc_link_rule($libname, $dbc_files)
#    emit_mindycomp_library_rule($libname, $source_files)
#    makegen_include($include_file)
#    unknown_platform_error()
#    convert_path_separator($path)
#
# Complete documentation on these functions can be found at the end of
# this file.  Note that many functions can take a bunch of file names.
# Some do this by accepting a single string with the filenames
# separated by spaces; others accept a variable number of arguments
# with one argument per filename.  The former style uses a variable
# beginning with a dollar sign ($), the latter style uses variables
# beginning with an at (@) sign.  (Ideally there'd be some
# consistency, but that's asking an awful lot...)
#
# ### How hard could it be to use only varargs?  It looks like only
# the mindycomp ones violate this convention...
#
# gen-makefile also defines a number of global variables.  Most of the
# variables of interest to Defaults and Makegen writers can be found
# in set_default_defaults().  Some other ones of interest:
#
#     $target_name
#         The name of the target platform, which is used to calculate
#         %target_platform.
#     $host_name
#         The name of the host platform, which is used to calculate
#         %host_platform.  This variable defaults to $target_name.
#     %target_platform
#         The platforms.descr entry for the target platform.  Keywords
#         are lowercase and use underbars instead of dashes.  This
#         variable is automatically derived from $target_name.
#     %target_features
#         The features this target platform defines.  Usage:
#         $target_features{'some_feature'} is 1 if defined, 0
#         otherwise.  (Note that the feature names are lowercase and
#         use underbars instead of dashes) This variable is
#         automatically derived from %target_platform.
#     %host_platform
#         The platforms.descr entry for the host platform.  Keywords
#         are lowercase and use underbars instead of dashes.  This
#         variable is automatically derived from $host_name.
#     $srcroot
#         The root of the source tree.  Must be an absolute path.
#     $buildroot
#         The root of the build tree.  Must be an absolute path, and
#         it must be equivalent to the directory mk-build-tree was run
#         from.  The purpose of this variable is to allow the user to
#         choose an alternate wording of the build root -- ie,
#         "/afs/cs/whatever" instead of "/afs/cs.cmu.edu/whatever".
#     $subdir
#         The source dir for this Makegen file is located at
#         "$srcroot/$subdir", and the current build dir is located at
#         "$buildroot/$subdir".
#     $destdir
#         The root directory that files will be copied to when "make
#         install" is run.  a.k.a. the "exec prefix"
#     $bindir
#     $libdir
#     $includedir
#     $sysconfdir
#         Directories in which we install dylan executables, libraries,
#         include files and platforms.descr.
#         Defaults to $destdir/bin, $destdir/lib/dylan, $destdir/include,
#         $destdir/etc.
#     $gen_makefile
#         Where to run gen-makefile from for calls from makefiles.
#     $shared
#         True if shared libraries will be built.
#     $static
#         True if static libraries will be built.
#
# In addition, the d2c Makegens use the following variables:
#
#     $enable_d2c
#     $enable_mindy
#     $enable_mindy_bootstrap


# Top level code
#
# gen-makefile uses the following global variables internally:
#
#     %all_platforms
#         Keys are names of platforms, value is 1 if we know of the
#         platform, 0 otherwise.
#     LIDFILE
#         A file handle that has special meaning in some places.
#     $orig_buildroot
#         Used to make sure $buildroot isn't changed by a Makegen.
#     $defaults
#         Full path name of the Defaults file.
#     $makefile
#         The name of the makefile we're currently generating.
#     $dot_obj
#         Shorthand for $target_platform{'object-filename-suffix'}.
#     $dot_exe
#         Shorthand for $target_platform{'executable-filename-suffix'}
#     $dot_lib
#         Shorthand for $target_platform{'library-filename-suffix'}
#     $lib_prefix
#         Shorthand for $target_platform{'library-filename-prefix'}
#     $lidfile_hit_eof
#         A global that gets around the lack of multiple return values.
#     $platforms_dot_descr
#         Full path name of "platforms.descr"
#
# gen-makefile also eval's into existence the following sets of variables:
#
#     %platform_platformName
#         where "platformName" is the name of a platforms.descr entry.
#     @makefileTarget_dependencies
#         where "makefileTarget" is the name of a Makefile target
#     @makefileTarget_commands
#         where "makefileTarget" is the name of a Makefile target
#
# gen-makefile supports the following top-level makefile targets:
# compile (default), install, clean, and cc_files (compiles C code
# generated by d2c).

$usage_message = "Usage: $0 [-pPlatforms.descr] [directory]\n";

@ARGV <= 2 || die $usage_message;
if ($ARGV[0] =~ /-p(.*)/) {
    $platforms_dot_descr = $1;
    shift(@ARGV);
} else {
    $platforms_dot_descr = ($ENV{'DYLANDIR'} || '/usr') .
        '/share/dylan/platforms.descr';
}
if (@ARGV == 0) {
    push(@ARGV, '.');
}
@ARGV == 1 || die $usage_message;
chdir($ARGV[0]) || die "Can't cd to $ARGV[0]: $!\n";

$lidfile_hit_eof = 0;
$lidfile_line = 1;

do set_default_defaults();
do find_Defaults_file();
do read_Defaults_file();
do do_Makegen_file();
exit(0);


# Core functionality of gen-makefile

# set_default_defaults() -- internal
#
# Sets up the default values we use if something isn't specified in
# the Defaults file.  There is no return value, only a lot of side
# effects to global variables.
#
sub set_default_defaults {
    # Set the default defaults.
    $DYLANDIR = 0;
    $D2C = '$(DYLANDIR)/bin/d2c';
    $D2CFLAGS = '-L$(DYLANDIR)/lib/dylan';
    $CFLAGS = '-g';
    $CPPFLAGS = '-I$(DYLANDIR)/include';
    $MELANGE = 'melange';
    $PARSERGEN = 'parsergen';
    $MC = 'mindycomp';
    # There is no MINDYFLAGS, because that depends on the library name.
    $MINDY = 'mindy';
    $gen_makefile = 'gen-makefile';
    $install_data = "cp";
    $install_program = "install";
    $install_library = "install";
    $compiler_debug_flags = " ";
}

# find_Defaults_file() -- internal
#
# Finds the Defaults file and sets $defaults, $buildroot, and
# $orig_buildroot appropriately.
#
sub find_Defaults_file {
    local($cwd) = cwd();  # cwd is more portable than `pwd`;
    chomp($cwd);          # chomp is safer than chop
    $buildroot = $cwd;
    $subdir = '';

    # Change //d/path into d:/path on NT.  Only a problem on drives
    # other than C:.  (We can't conditionalize this because we don't
    # yet know which platform we're running on.)
    $buildroot =~ s|^//(\w)/|\1:/|;

    until (-e ($defaults = $buildroot . '/Defaults')) {
        ($buildroot =~ /^(.*)\/([^\/]+)$/)
            || die("Can't find Defaults and hence can't tell where "
                   . "the root is.\n");

        $subdir = $2 . '/' . $subdir;
        $buildroot = $1;
    }

    # Save the buildroot.
    $orig_buildroot = $buildroot;
}

# read_Defaults_file() -- internal
#
# Reads in the Defaults file which find_Defaults_file found, and sets
# up a variety of global variables that depend on things in the
# Defaults file.
#
sub read_Defaults_file {
    # Slurp in the defaults.
    do $defaults;
    die("Problem loading $defaults:\n  $@\n") if $@;

    &read_platforms_dot_descr();
    $target_name || die("Must set variable \$target_name in Defaults file\n");
    $host_name = $host_name || $target_name;  # $host defaults to $target
    %target_platform = &get_platform($target_name);
    %host_platform = &get_platform($host_name);

    # Extract features from target platform
    local($feature_name);
    for $feature_name (split(/\s+/, $target_platform{'default_features'})) {
        $compiler_debug_flags = "$compiler_debug_flags -D$feature_name ";
        $feature_name =~ tr/-A-Z/_a-z/;
        $features{$feature_name} = 1;
    }

    $target_platform{'platform_name'} =~ /\-(\w+)\-/;
    $debug_platform_name = $1;
    $debug_platform_name =~ tr/a-z/A-Z/;
    $debug_platform_name = "-DGD_PLATFORM_$debug_platform_name";

    # Create some useful shorthands (internal use only)
    $makefile = $host_platform{'makefile_name'};
    $dot_obj = $target_platform{'object_filename_suffix'};
    $dot_exe = $target_platform{'executable_filename_suffix'};

    # There can be multiple library suffixes, and we only need the
    # preferred one.
    ($dot_lib) = split(/\s+/, $target_platform{'library_filename_suffix'});
    $lib_prefix = $target_platform{'library_filename_prefix'};

    unless($target_platform{'link_shared_library_command'} ne '') {
        $shared = 0;
    }

    if ($shared) {
        $dot_obj = $target_platform{'shared_object_filename_suffix'};
        ($dot_lib) = split(/\s+/,
                           $target_platform{'shared_library_filename_suffix'});
    }

    # Check to see if buildroot changed to something else.
    unless ($buildroot eq $orig_buildroot) {
        ### On win32, this code doesn't appear to work correctly in
        ### that it *always* thinks the two are equivalent.  Oh well,
        ### no harm done...
        local($root_inode) = (stat($buildroot))[1];
        local($orig_inode) = (stat($orig_buildroot))[1];
        unless ($root_inode == $orig_inode) {
            die("Defaults changed \$buildroot to:\n  $buildroot\n"
                . "but that is a different directory than:\n"
                . "  $orig_buildroot\n");
        }
        &assert_path_is_absolute($buildroot, "\$buildroot");
        $defaults = $buildroot . '/Defaults';
    }

    # Make sure they set srcroot.  And set it to an absolute pathname.
    $srcroot || die('$srcroot not set in Defaults.\n');
    &assert_path_is_absolute($srcroot, "\$srcroot");

    # Tack the subdir onto srcroot.  Note: subdir is empty or ends
    # with a /, hence the chop.
    
    chop($srcdir = $srcroot.'/'.$subdir);

    # Likewise for builddir
    chop($builddir = $buildroot.'/'.$subdir);

    if ($destdir) {
      unless ($libdir) {
        $libdir = $destdir.'/lib/dylan';
      }
      unless ($bindir) {
        $bindir = $destdir.'/bin';
      }
      unless ($includedir) {
        $includedir = $destdir.'/include';
      }
      unless ($sysconfdir) {
        $sysconfdir = $destdir.'/etc';
      }
    }
    
    $srcroot = &convert_path_separator($srcroot);
    $srcdir = &convert_path_separator($srcdir);
    $buildroot = &convert_path_separator($buildroot);
    $builddir = &convert_path_separator($builddir);
    $destdir = &convert_path_separator($destdir);
    $libdir = &convert_path_separator($libdir);
    $bindir = &convert_path_separator($bindir);
    $includedir = &convert_path_separator($includedir);
    $sysconfdir = &convert_path_separator($sysconfdir);

    $MC = &convert_path_separator($MC);
    $MINDY = &convert_path_separator($MINDY);
    $D2C = &convert_path_separator($D2C);
    $MELANGE = &convert_path_separator($MELANGE);
    $PARSERGEN = &convert_path_separator($PARSERGEN);
            
}

# do_Makegen_file() -- internal
#
# Reads in the Makegen file and creates an appropriate Makefile.
#
sub do_Makegen_file {
    # Find the Makegen file.
    local($makegen) = $srcdir . '/Makegen';
    (-e $makegen) || die("No $makegen\n");

    # Start the makefile.
    open(MAKEFILE, ">,$makefile") || die("Can't open ,$makefile: $!\n");
    select(MAKEFILE);

    print "### This makefile is machine generated.  Don't expect any edits to survive.\n";
    print "### Generated for target $target_name hosted by $host_name\n\n";

    print "SRCROOT=$srcroot\n";
    print "SRCDIR=$srcdir\n";
    print 'VPATH=$(SRCDIR)' . "\n";
    print "BUILDROOT=$buildroot\n";
    print "top_builddir=$buildroot\n";
    print "BUILDDIR=$builddir\n";
    print "DESTDIR=\n"; # GNU DESTDIR, not the $destdir variable.
    if ($host_platform{'make_supports_phony_targets?'}) {
        print "\n.PHONY: default compile install uninstall clean\n\n";
    }
    print "default: compile\n\n";

    # Slurp in the generator.
    do $makegen;
    die("Problem loading $makegen:\n  $@\n") if $@;

    if (@files_to_clean) {
        push(@clean_commands, "-rm -f @files_to_clean");
        if ($shared) {
            push(@clean_commands, "-rm -rf .libs");
        }
    }
    
    push(@GNUmakefile_dependencies, $gen_makefile,
         $defaults, $makegen);
    push(@GNUmakefile_commands,
        "$gen_makefile -p$platforms_dot_descr .");

    &emit_rule('compile');
    $destdir && do emit_rule ('install');
    $destdir && do emit_rule ('uninstall');
    &emit_rule('clean');
    if ($host_platform{'makefiles_can_rebuild_themselves?'}) {
        print $makefile, ': ', join(' ', eval('@GNUmakefile_dependencies'));
        print "\n";
        foreach $rule (eval('@GNUmakefile_commands')) {
            print "\t", $rule, "\n";
        }
    print "\n";
    }

    &emit_rule('cc_files');
    if ($enable_mindy) {
        &emit_rule('dbc_only');
    }
    close(MAKEFILE);
    rename(",$makefile", $makefile);
}


# Internal utility functions

# assert_path_is_absolute($path, $variable) -- internal
#
# Decides if the path is an absolute path; signals an error if it
# isn't.  On a win32 system, an absolute path must have a drive
# letter.  Make sure you've set %host_platform before you call this
# function.  $variable is used only in error messages.
#
sub assert_path_is_absolute {
    local($path, $variable) = @_;
    local($win32_absolute) = ($path =~ /^w:\//);
    local($unix_absolute) = ($path =~ /^\//);
    local($ok, $explanation);

    # We do a lot of extra work here so we can provide better error
    # messages.
    if ($win32_absolute && $host_platform{'uses_drive_letters?'}) {
        $ok = 1;
    } elsif ($unix_absolute && !$host_platform{'uses_drive_letters?'}) {
        $ok = 1;
    } elsif ($unix_absolute && $host_platform{'uses_drive_letters?'}) {
        $ok = 0;
        $explanation = "(You must include a drive letter)";
    } elsif ($win32_absolute && !$host_platform{'uses_drive_letters?'}) {
        $ok = 0;
        $explanation = "(You included a drive letter on a Unix machine?!?)";
    } else {
        # path is not absolute by anyone's definition, which doesn't
        # merit further explanation.
        $ok = 0;
        $explanation = "";
    }   

 $ok = 1;
    if (! $ok) {
        if ($explanation) {
            die "$variable is not absolute:\n  $path\n$explanation\n";
        } else {
            die "$variable is not absolute:\n  $path\n";
        }
    }
}

# emit_rule($target) -- internal
#
# Emits a rule, looking at the global state to decide what the
# dependencies are and how to build the target.
#
sub emit_rule {
    local($rule) = @_;

    print $rule, ': ', join(' ', eval('@'.$rule.'_dependencies')), "\n";
    foreach $rule (eval('@'.$rule.'_commands')) {
        print "\t", $rule, "\n";
    }
    print "\n";
}

# compile_subdir($subdir) -- internal
#
# Generates the makefile rule to visit a single subdirectory.
#
sub compile_subdir {
    local($subdir) = &convert_path_separator(@_);
    push(@compile_commands, 
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, ""));
    push(@install_commands, 
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, "install"));
    unshift(@uninstall_commands, 
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, "uninstall"));
    push(@clean_commands, 
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, "clean"));
    push(@cc_files_commands,
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, "cc_files"));
    push(@dbc_only_commands,
         sprintf($host_platform{'recursive_make_command'}, 
                 $subdir, "dbc_only"));
}

# maybe_emit_d2c_defines() -- internal
#
# If they haven't already been emitted, emit the Makefile header,
# which defines various Makefile variables like $(D2C).
#
sub maybe_emit_d2c_defines {
    unless ($d2c_defines) {
        if ($CC) {
            print("CC=$CC\n");
        }
        if($enable_debug) {
            $CFLAGS = $target_platform{'default_c_compiler_debug_flags'};
        } elsif ($enable_profiling) {
            $CFLAGS = $target_platform{'default_c_compiler_profile_flags'};
        } else {
            $CFLAGS = $target_platform{'default_c_compiler_flags'};
        }
        $CFLAGS =~ s/-I%s//;
        print <<EOF;
D2C = $D2C $compiler_debug_flags
D2CFLAGS = $D2CFLAGS
CFLAGS = $CFLAGS
CPPFLAGS = $CPPFLAGS $debug_platform_name
COPTS = $COPTS
MELANGE = $MELANGE
PARSERGEN = $PARSERGEN
MC = $MC $compiler_debug_flags -Dmindy
MINDY = $MINDY

EOF
        if ($host_platform{'environment_variables_can_be_exported?'}) {
			local($EXPORT = "export");
        	if ($target_name eq 'x86-win32-vc') {
				$EXPORT="set";
			}
            print "$EXPORT CCFLAGS = \$(CFLAGS) \$(CPPFLAGS) \$(COPTS)\n\n";
            if ($LIBTOOL) {
                print "$EXPORT LIBTOOL = $LIBTOOL\n\n";
            }
            if ($DYLANPATH) {
                print "$EXPORT DYLANPATH = $DYLANPATH\n\n";
            };
            if ($DYLANDIR) {
                print "$EXPORT DYLANDIR = $DYLANDIR\n\n";
            }
        } else {
            print "LIBTOOL = $LIBTOOL\n\n";
        }
        $d2c_defines = 1;
    }
}

# parse_dylan_header() -- internal
#
# Reads the Dylan header (keyword: value, ...) from the file LIDFILE,
# which is already open.  (The file doesn't have to be a lid file, it
# could be something else like a platforms.descr) Returns an
# associative array where the keys are header keywords (mashed to
# lower case), and the values are the header values.
#
# In contrast to the Dylan version, keywords can not appear more than
# once.  If they do, the last value will be used.  Multi-line values
# are supported, though.
#
# We also set the global $lidfile_hit_eof, so you can know why the
# header ended.  This is useful for reading in platform.descr files,
# where you call parse_dylan_header repeatedly.
#
sub parse_dylan_header {
    local(%contents);
    local($last_keyword); # for multi-line values

    while (<LIDFILE>) {
        $lidfile_line = $lidfile_line + 1;
        # remember, in Perl "." is any character other than newline.
        # Also, Perl handles all newline hassles by turning win32
        # style CRLFs into LFs (\n's) before it shows us the string.
	
        if ( ($target_name eq 'x86-cygnus-gcc') || ($target_name eq 'x86-win32-vc')) {
            $_ =~ s/\r//g;      # Get rid of bogus carriage returns
        }

        if (/^\s*$/) {  # if blank line, break out of loop
            $lidfile_hit_eof = 0;
            return %contents;
        } elsif (m|^//.*$|) {
            # comment line, ignore
        } elsif (/^\s+(.*)$/) {
            # Continuation line -- part of a multi-line value
            $contents{$last_keyword} .= ' ' . $1;
        } else {
            /^([-A-Za-z0-9_!&*<>|^\$\%\@\?]+):\s+(.*)\s+$/
                || die "Bad keyword line in $lid_file, line $lidfile_line\n";
            local($keyword) = $1;
            local($value) = $2;
            if ($value eq '#f' | $value eq '#F') {
                $value = 0;
            } elsif ($value eq '#t' | $value eq '#T') {
                $value = 1;
            }
            $keyword =~ tr/-A-Z/_a-z/;
            $contents{$keyword} = $value;
            $last_keyword = $keyword;
        }
    }
    $lidfile_hit_eof = 1;
    return %contents;
}

# parse_lid_file($filename) -- internal
#
# Reads in the LID file, and returns an associative array where the
# keys are header keywords (mashed to lower case), and the values are
# the header values.  As a magic special case, the keyword 'files'
# contains all the files in the body of the lid file.
#
sub parse_lid_file {
    local($lid_file) = @_;
    local(%contents);
    local ($dot_static) = $target_platform{'object_filename_suffix'};

    open(LIDFILE, $srcdir.'/'.$lid_file.'.lid')
      || die("Can't open $lid_file.lid: $!\n");

    %contents = &parse_dylan_header();

    # Read the filenames
    # a .o in the Lid file is a hack to get a foreign .o put in the archive.
    # Just ignore em, since there should be a seperate C rule to clean, etc.
    while (<LIDFILE>) {
        $lidfile_line = $lidfile_line + 1;

        if ( ($target_name eq 'x86-cygnus-gcc') || ($target_name eq 'x86-win32-vc')) {
            $_ =~ s/\r//g;      # Get rid of bogus carriage returns
        }

        if ($_ !~ m/.*$dot_obj$/ && $_ !~ m/.*$dot_static$/) {
            chop; # kill newline
            $contents{'files'} .= " $_";
        }
    }

    # replace multiple spaces with single spaces
    $contents{'files'} =~ s/\s+/ /g;

    # strip leading whitespace, which tends to screw up other parts of
    # gen-makefile
    $contents{'files'} =~ s/^\s+//;

    close(LIDFILE);
    return %contents;
}

# read_platforms_dot_descr() -- internal
#
# Read the platforms.descr file (used to be called targets.descr).
# Each platform description will get it's own associative array named
# %platform_platformName.  (For example, %platform_x86_win32) See
# parse_dylan_header for a description of the arrays.
#
# In addition to the per-platform arrays, there is a global
# associative array %all_platforms which is used to list all known
# platforms.  The key is the name of a platform; the value is 1 if it
# is a known platform, 0 otherwise.
#
sub read_platforms_dot_descr {
    ### Should have better way of specifying location of .descr
    open(LIDFILE, $platforms_dot_descr)
      || die("Can't open $platforms_dot_descr: $!\n");
    while (! $lidfile_hit_eof) {
        local(%platform) = &parse_dylan_header();
        local(@keywords) = keys(%platform);
        if ($#keywords > 0) {
            # if a real header, not a blank line or comment block
            local($platform_name) = $platform{'platform_name'};
            $platform_name =~ tr/-A-Z/_a-z/; # mash to lowercase; - to _

            if ($platform{'inherit_from'}) {
                local($key);
                foreach $parent (split(/\s+/, $platform{'inherit_from'})) {
                    local(%parent_plat) = &get_platform($parent);
                    local($key);
                    foreach $key (keys(%parent_plat)) {
                        if (!defined $platform{$key}) {
                            $platform{$key} = $parent_plat{$key};
                        } elsif ($key eq 'default_features') {
                            $platform{$key} = 
                                $platform{$key} . " " . $parent_plat{$key};
                        }
                    }
                }
            }

            # Copy the %platforms array into the appropriate global
            local($eval_string) = '%' . 'platform_' . $platform_name 
                . ' = %' . 'platform';
            eval($eval_string);
            $all_platforms{$platform_name} = 1;
        }
    }
    close(LIDFILE);
}

# get_platform($platform_name) -- internal
#
# Return the platform (associative array) that corresponds to
# $platform_name.  This function will lowercase the platform name and
# translate dashes into underbars for you.
#
sub get_platform {
    local($platform_name) = @_;
    $platform_name =~ tr/-A-Z/_a-z/;
    $all_platforms{$platform_name} || die("Unknown platform: $platform_name");
    local(%platform) = eval('%platform_' . $platform_name);
    return %platform;
}


# Subroutines people can call from Makegen files.  See top of file for
# summary.

# compile_subdirs(@subdirs) -- exported
#
# Generates the makefile rule to visit a bunch of subdirs subdirectory.
#
sub compile_subdirs {
    local($dir);
    foreach $dir (@_) {
        do compile_subdir($dir);
    }
}

# emit_library_rule($lidfile, $extradeps, $extraflags, @keywords) -- exported
#
# Emit the Makefile rule to build a Dylan library.  $lidfile is the
# name of the lidfile, $extradeps are dependencies in addition to the
# lid file and the "obvious" dependencies (used libraries and any file
# mentioned in the lid file).  $extraflags are any extra flags you
# want to pass to d2c, esp. "-L../somedir".  $extradeps are any
# additional dependencies you want to specify for the library being
# built (such as $buildroot/force.timestamp).
#
# Keywords recognized:
#
#    'compile'
#         Compile this with d2c and add it to the list of libraries compiled by
#         the 'compile' makefile target.
#    'install' 
#         Adds this library/executable to the list of files installed
#         by the 'install' makefile target.
#    'no-mindy'
#         By default, a target is created for dbc_only; this target 
#         is not added to any makefile targets, but it can be manually
#         created by running "make dbc_only".  This makes for easy debugging.
#         'no-mindy' suppresses the creation of this target, which is useful
#         when the Mindy version of the library does not use the same source
#         files as the d2c version.
#    'compile-mindy'
#         Add libname.dbc to the 'compile' makefile target (possibly in
#         addition to the d2c version of the library.)  This flag does not make 
#         much sense with 'no-mindy'.
#    'no-d2c'
#         Like no-mindy, only suppress emission of d2c rules.
#
# The return value is a two element array.  The first element is the
# target which would have been used if the 'compile' flag were
# specified.  The second element of the array is the target which
# would have been used if the 'compile-mindy' flag were specified.
# This return value is most useful when bootstrapping a compiler from
# Mindy, but I'm sure others can think of additional uses.
#
sub emit_library_rule {
    local($lidfilename, $extradeps, $extraflags, @keywords) = @_;

    do maybe_emit_d2c_defines();

    local(%lidfile) = &parse_lid_file($lidfilename);
    local($library) = $lidfile{'library'} 
                          || die("No library: header in $lidfilename.lid.\n");
    $library =~ tr/A-Z/a-z/;
    local($unit_prefix);
    if($enable_bootstrap && $bootstrap_compiler eq "d2c" && $lid_format_version < 2) {
        $unit_prefix = $lidfile{'unit_prefix'} || "$library";
    } else {
        $unit_prefix = "$library";
    };
    local($executable) = $lidfile{'executable'};
    local($executable_filename) = $executable . $dot_exe;
    local($shared_library) = $shared && ($lidfile{'shared_library'} ne 'no');
    local($dot_static) = $target_platform{'object_filename_suffix'};
    
    local($d2c_target);

    if ($enable_d2c) {
        local($file);
        for $file (split(' ', $lidfile{'files'})) {
            $file =~ s/\.dylan$//;  # chop off ".dylan"
			if ($&) {				# This was a "something.dylan"
	            push(@files_to_clean, "$file.c", "$file$dot_obj");
			} else {
				# This is a *.o or something that is handled by the
				# emit_c_file_rule ...
				next;
			}
            if ($shared && $static) {
                push(@files_to_clean, "$file$dot_static");
            }
        }

        push(@files_to_clean,
             "$unit_prefix-init.c", "$unit_prefix-init$dot_obj");
        push(@files_to_clean,
             "$unit_prefix-heap.c", "$unit_prefix-heap$dot_obj");

        if($shared && $static) {
            push(@files_to_clean, "$unit_prefix-init$dot_static");
            push(@files_to_clean, "$unit_prefix-heap$dot_static");
        }

        if ($executable) {
            push(@files_to_clean, $executable_filename, 'heap.c', 'inits.c');
            if($shared && $static) {
                push(@files_to_clean, "heap$dot_static", "inits$dot_static");
            }
            push(@files_to_clean, "heap$dot_obj", "inits$dot_obj");

            if (grep(/^compile$/, @keywords)) {
                push(@compile_dependencies, $executable_filename);
                grep(/^install$/, @keywords)
                    && do install_executable($bindir, $executable_filename);
            }

            $d2c_target = $executable_filename;
        }
        else {
            local($foo_lib_du, $libfoo_a);
            
            $foo_lib_du = "$library.lib.du";
            $libfoo_a = "$lib_prefix$unit_prefix-dylan$dot_lib";
            
            push(@files_to_clean, $foo_lib_du, $libfoo_a);

            if($shared && $static) {
                local($lib_static)
                    = $target_platform{'library_filename_suffix'};
                push(@files_to_clean, "$lib_prefix$unit_prefix-dylan$lib_static");
            }
            
            if (grep(/^compile$/, @keywords)) {
                push(@compile_dependencies, $foo_lib_du);
                if (grep(/^install$/, @keywords)) {
                    do install($libdir, $foo_lib_du);
                    do install_library($libdir, $libfoo_a, $shared_library);
                }
            }
            
            $d2c_target = $foo_lib_du;
        }
    }

    if (! grep(/^no-d2c$/, @keywords) && $enable_d2c) {
        print "$d2c_target: $srcdir/$lidfilename.lid $extradeps\n";
        my $profile_flag = $enable_profiling ? "--profile" : "";
        if ($shared_library) {
            print "\t\$(D2C) -M $profile_flag \$(D2CFLAGS) --rpath=$libdir $extraflags ",
                "$srcdir/$lidfilename.lid\n";
            if ($static && !$executable) {
                local ($dot_lib)
                    = split(/\s+/,
                            $target_platform{'library_filename_suffix'});
                print "\trm -f $lib_prefix$unit_prefix-dylan$dot_lib\n";
                print "\tln -s .libs/$lib_prefix$unit_prefix-dylan$dot_lib",
                    " $lib_prefix$unit_prefix-dylan$dot_lib\n";
                push(@files_to_clean, " $lib_prefix$unit_prefix-dylan$dot_lib");
            }
        } else {
            print "\t\$(D2C) -M $profile_flag \$(D2CFLAGS) --static $extraflags ",
                "$srcdir/$lidfilename.lid\n";
        }
        local ($make_name) = $host_platform{'make_command'};
        local($cc_files_cmd) = "$make_name -f cc-" . $unit_prefix
            . "-files.mak";
        push(@cc_files_commands, $cc_files_cmd);

        print "\ninclude $unit_prefix.dep\n\n";

        unless (-e "$unit_prefix.dep") {
            open(DEP, ">$unit_prefix.dep")
                || die("Can't create $unit_prefix.dep: $!\n");
            close(DEP);
        }
    }

    if (! grep(/^no-mindy$/, @keywords) && $enable_mindy) {
        &emit_mindycomp_library_rule($library, $lidfile{'files'});
    }
    if (grep(/^compile-mindy$/, @keywords) && $enable_mindy) {
        push(@compile_dependencies, "$library-lib.dbc");
        grep(/^install$/, @keywords)
            && do install($libdir, "$library-lib.dbc");
    }
    local(@return_val);
    if ($enable_d2c) {
        push(@return_val, $d2c_target);
    }
    if ($enable_mindy) {
        push(@return_val, "$library-lib.dbc");
    }
    return @return_val;
}

# emit_c_file_rule($base_file_name, @header_files) -- exported
#
# Emit the rule to compile a C file into an object file.  Dependencies
# will be the source file and all @header_files.
#
sub emit_c_file_rule {
    local ($basefile, @headers) = @_;
    local ($objfile) = "$basefile$dot_obj";

    do maybe_emit_d2c_defines();

    print "$objfile: \$(SRCDIR)/$basefile.c";
    foreach $header (@headers) {
        print " \$(SRCDIR)/$header";
    }
    if ($shared) {
        print "\n\t\$(LIBTOOL) \$(CC) \$(CCFLAGS) -I\$(SRCDIR)";
    } else {
        print "\n\t\$(CC) \$(CCFLAGS) -I\$(SRCDIR)";
    }
    print " -c \$(SRCDIR)/$basefile.c -o $objfile\n\n";
    if ($target_name eq $host_name) {
      push(@compile_dependencies, $objfile);
    } else {
      push(@cc_files_dependencies, $objfile);
    };

    push(@files_to_clean, $objfile);

    if($static && $shared) {
        local ($dot_static) = $target_platform{'object_filename_suffix'};
        push(@files_to_clean, "$basefile$dot_static");
    }
}

#
# Emit the rule to compile an assembly file into an object file.  Dependencies
# will be the source file and all @header_files.
#
sub emit_asm_file_rule {
    local ($basefile, $srcfile, @headers) = @_;
    local ($objfile) = "$basefile$dot_obj";

    do maybe_emit_d2c_defines();

    print "$objfile: \$(SRCDIR)/$srcfile.s";
    foreach $header (@headers) {
        print " \$(SRCDIR)/$header";
    }
    if ($shared) {
        print "\n\t\$(LIBTOOL) \$(CC) \$(CFLAGS) \$(CPPFLAGS) -I\$(SRCDIR)";
    } else {
        print "\n\t\$(CC) \$(CFLAGS) \$(CPPFLAGS) -I\$(SRCDIR)";
    }
    print " -c \$(SRCDIR)/$srcfile.s -o $objfile\n\n";
    if ($target_name eq $host_name) {
      push(@compile_dependencies, $objfile);
    } else {
      push(@cc_files_dependencies, $objfile);
    };

    push(@files_to_clean, $objfile);
}

#
# Emit the rule to compile an assembly file (that requires
# preprocessing) into an object file.  Dependencies will be the source
# file and all @header_files.
#
sub emit_cppasm_file_rule {
    local ($basefile, $srcfile, @headers) = @_;
    local ($objfile) = "$basefile$dot_obj";

    do maybe_emit_d2c_defines();

    print "$objfile: \$(SRCDIR)/$srcfile.s";
    foreach $header (@headers) {
        print " \$(SRCDIR)/$header";
    }
    if ($shared) {
        print "\n\t\$(LIBTOOL) \$(CC) \$(CFLAGS) \$(CPPFLAGS) -I\$(SRCDIR)";
    } else {
        print "\n\t\$(CC) \$(CFLAGS) \$(CPPFLAGS) -I\$(SRCDIR)";
    }
    print " -x assembler-with-cpp";     # Added to provide the "cpp" part
    print " -c \$(SRCDIR)/$srcfile.s -o $objfile\n\n";
    if ($target_name eq $host_name) {
      push(@compile_dependencies, $objfile);
    } else {
      push(@cc_files_dependencies, $objfile);
    };

    push(@files_to_clean, $objfile);
}

# emit_cc_file_rule($base_file_name, @header_files) -- exported
#
# Just like emit_c_file_rule, except uses .cc as an extension instead
# of .c (C++ files instead of C files).  I don't think this function
# is currently used by any of our Makegens.
#
sub emit_cc_file_rule {
    local ($basefile, @headers) = @_;
    local ($objfile) = "$basefile$dot_obj";

    do maybe_emit_d2c_defines();

    print "$objfile: \$(SRCDIR)/$basefile.cc";
    foreach $header (@headers) {
        print " \$(SRCDIR)/$header";
    }
    if ($shared) {
        print "\n\t\$(LIBTOOL) \$(CC) \$(CFLAGS) -I\$(SRCDIR) ";
    } else {
        print "\n\t\$(CC) \$(CFLAGS) -I\$(SRCDIR) ";
    }
    print "-c \$(SRCDIR)/$basefile.cc -o $objfile\n\n";
    if ($target_name eq $host_name) {
      push(@compile_dependencies, $objfile);
    } else {
      push(@cc_files_dependencies, $objfile);
    };
    push(@files_to_clean, $objfile);
}

# emit_melange_rule($base_file_name, @dependencies) -- exported
#
# Emits the rule to turn a Melange input (.intr) file into a .dylan
# file.
#
# stopgag measure, FIXME:
# this is disabled until melange can actually cope with all the header
# files, and the build order is fixed so melange gets built before
# the libraries needing it.
sub emit_melange_rule {
#    local ($basefile, @headers) = @_;
#    do maybe_emit_d2c_defines();

#    print "\$(SRCDIR)/$basefile.dylan: \$(SRCDIR)/$basefile.intr";
#    foreach $header (@headers) {
#       print " \$(SRCDIR)/$header";
#    }
#    print "\n\t\$(MELANGE) -I\$(SRCDIR) ";
#    print "\$(SRCDIR)/$basefile.intr \$(SRCDIR)/$basefile.dylan\n\n";
#    push(@compile_dependencies, "\$(SRCDIR)/$basefile.dylan");
#    push(@files_to_clean, "\$(SRCDIR)/$basefile.dylan");
}

# emit_parsergen_rule($base_file_name) -- exported
#
# Emit the rule to Melange a .input file into a .dylan file.
#
sub emit_parsergen_rule {
    local ($basefile) = @_;
    do maybe_emit_d2c_defines();

    print "$basefile.dylan: \$(SRCDIR)/$basefile.input";
    print "\n\t\$(PARSERGEN) ";
    print "\$(SRCDIR)/$basefile.input $basefile.dylan\n\n";
    push(@compile_dependencies, "$basefile.dylan");
    push(@files_to_clean, "$basefile.dylan");
}

# install_one_file($from_file, $to_file, $executable_permissions) -- internal
#
# Emits the rule to copy $from_file into $to_file when "make install"
# is run.  If $executable_permissions is true (non-zero), then the
# installed file will have executable permissions.
#
# This may seem like a good candidate for moving to platforms.descr.
# Unfortunately, there's a lot more going on than merely choosing the
# name of the "copy file" command.
#
sub install_one_file {
    local ($from_file, $to_file, $executable_permissions) = @_;

    # GNU DESTDIR, not the $destdir variable.
    $to_file = "\${DESTDIR}$to_file";

    print "\n$to_file: $from_file\n";
    if ($host_platform{'default_features'} =~ /compiled-for-win32/i) {
        # Host is a win32 platform
        local ($rule) = "\tcopy $from_file $to_file\n\n";
        $rule =~ tr|/|\\|;  # translate / to \ for MS-Windows
        print $rule;
    } elsif ($host_platform{'default_features'} =~ /compiled-for-unix/i) {
        if ($executable_permissions) {
            print "\t$install_program $from_file $to_file\n\n";
        } else {
            print "\t$install_data $from_file $to_file\n\n";
        };
    } else {
        die("The host is neither Unix nor win32, so I don't know how\n"
            . "to install your files.");
    }
    push(@install_dependencies, $to_file);
    push(@uninstall_commands, "rm -f $to_file");
}

# install($dest_dir, @files) -- exported
#
# Installs a bunch of files from the build dir into $dest_dir, normally a
# subdirectory of $destdir.
#
sub install {
    local ($dest_dir, @files) = @_;
    local ($file, $dst);

    # Don't generate any install rules unless destdir is set.
    $destdir || return;

    foreach $file (@files) {
        $dst = "$dest_dir/$file";
        &install_one_file($file, $dst, 0);
    }
}

# install_executable($dest_dir, @files) -- exported
#
# Installs a bunch of files from the build dir into $dest_dir, normally a
# subdirectory of $destdir.  Makes them executable.
#
sub install_executable {
    local ($dest_dir, @files) = @_;
    local ($file, $dst);
    # Don't generate any install rules unless destdir is set.
    $destdir || return;

    foreach $file (@files) {
        $dst = "$dest_dir/$file";
        &install_one_file($file, $dst, 1);
    }
}

# install_from_src($dest_dir, @files) -- exported
#
# Installs a bunch of files from the source dir into dest_dir.  Useful when you
# just want to copy Perl scripts into the dest dir.
#
sub install_from_src {
    local ($dest_dir, @files) = @_;
    local ($file, $dst);

    # Don't generate any install rules unless destdir is set.
    $destdir || return;

    foreach $file (@files) {
        $dst = "$dest_dir/$file";
        &install_one_file("$srcdir/$file", $dst, 0);
    }
}

# install_executable_from_src($dest_dir, @files) -- exported
#
# Like install_from_src, except makes sure the file has executable 
# permissions for everyone (this is not relevant on win32, only unix).
#
sub install_executable_from_src {
    local ($dest_dir, @files) = @_;
    local ($file, $dst);

    # Don't generate any install rules unless destdir is set.
    $destdir || return;

    foreach $file (@files) {
        $dst = "$dest_dir/$file";
        &install_one_file("$srcdir/$file", $dst, 1);
    }
}

# install_library_from_src($dest_dir, $library, $shared_library) -- exported
#
# Installs library in the given location.  If it's shared, use libtool
# to do it.
#
sub install_library {
    local ($dest_dir, $library, $shared_library) = @_;

    # Don't generate any install rules unless destdir is set.
    $dest_dir || return;

    print "\n\${DESTDIR}$dest_dir/$library: $library\n";
    print "\t$install_library $library \${DESTDIR}$dest_dir/$library\n\n";
    push(@install_dependencies, "\${DESTDIR}$dest_dir/$library");
}


# emit_mindycomp_rule($dylan_source) -- exported
#
# Emits a rule to run mindycomp on the file.  $dylan_source shouldn't
# have an extension.  If the file doesn't exist in the source, hopefully
# someone will create it in the build area.
#
# emit_mindycomp_library_rule is the primary, perhaps even the only,
# user of this function.
#
sub emit_mindycomp_rule {
    local($source, $flags) = @_;
    if (-e "$srcdir/$source.dylan") {
      $da_source = "\$(SRCDIR)/$source.dylan";
    } else {
      $da_source = "$source.dylan";
    }
    print "$source.dbc: $da_source\n";
    print "\t\$(MC) $flags ";
    print "-o $source.dbc $da_source\n";
}

# emit_dbc_link_rule($libname, $dbc_files) -- exported
#
# Links a bunch of .dbc files into a single .dbc file.
#
sub emit_dbc_link_rule {
    local($libname, $objects) = @_;
    print "\n$libname: $objects\n";
    if ($host_platform{'use_dbclink?'}) {
        print "\tdbclink $libname $objects\n\n";
    } else {
        print "\tcat $objects > $libname\n\n";
    }
}

# emit_mindycomp_library_rule($libname, $source_files) -- exported
#
# Generates a rule for compiling the library with Mindy.  Usually this
# function is invoked via emit_library_rule, but you can call it on
# your own if you want.  The resulting file will be called
# foo-lib.dbc, and will *not* be added to the list of compile
# dependencies.  Also, this command only builds libraries, not
# executables.  To build an executable, we need to know all the other
# libraries that go into it, which is beyond the scope of this
# function.
#
sub emit_mindycomp_library_rule {
    local($libname, $lid_sources) = @_;
    $libname =~ tr/A-Z/a-z/;
    local($uppercase_libname) = $libname;
    $uppercase_libname =~ tr/-a-z/_A-Z/;
    local($file, @sources, @objects);
    foreach $file (split(/\s+/, $lid_sources)) {
        $file =~ s/\.dylan$//;  # chop off ".dylan"
        push(@sources, $file); # emit_mindycomp_rule doesn't want ".dylan"
        push(@objects, "$file.dbc");
        push(@files_to_clean, "$file.dbc");
    }
    print "DASH_$uppercase_libname = -l$libname\n\n";
    print "$uppercase_libname", "_OBJS = ", join(" ", @objects), "\n\n";
    push(@dbc_only_dependencies, "$libname-lib.dbc");
    push(@files_to_clean, "$libname-lib.dbc");

    local($source);
    foreach $source (@sources) {
        &emit_mindycomp_rule($source, "\$(DASH_$uppercase_libname)");
    }
    &emit_dbc_link_rule("$libname-lib.dbc", "\$(${uppercase_libname}_OBJS)");
}

# makegen_include($include_file) -- exported
#
# makegen_include is like C's #include.  The main reason to use
# makegen_include is that it correctly handles the case where the
# srcdir is not the same as the build dir (ie, it looks for the include
# file in the srcdir).
#
# There's a certain danger of variable capture with the variable
# $include_file, so don't use that variable in your included files.
#
sub makegen_include {
    local($include_file) =  @_;
    $include_file = $srcdir . '/' . $include_file;
    (-e $include_file) || die("No $include_file\n");
    push(@GNUmakefile_dependencies, $include_file);
    # Convert \ back to /, because that's how "do" likes its filenames
    $include_file =~ tr|\\|/|;
    do $include_file;
}

# unknown_platform_error() -- exported
#
# Tries to give a useful error message when a Makegen doesn't
# understand the platform.
#
sub unknown_platform_error {
    die "Unknown CPU type for platform " . $target_name . "\n"
        . "All known features: " . $target_platform{'default_features'} . "\n";
}

# convert_path_separator($path) -- exported
#
# Converts a path which uses / into one that uses the path separator
# appropriate for the host platform.
#
sub convert_path_separator {
    local($path) = @_;
    local($separator) = $host_platform{'path_separator'};
    $separator =~ s|\\\\|\\|g; # translate \\ into \ (Dylan escaping rules)
    $path =~ s|/|$separator|g;
    return $path;
}
