Below is the file 'Attic/configure.pl' from this revision. You can also download the file.

#!/usr/bin/perl -w

require 5.006;

use strict;
use Getopt::Long;
use File::Spec;
use File::Copy;

my $MAJOR_VERSION = 1;
my $MINOR_VERSION = 5;
my $PATCH_VERSION = 12;

my $VERSION_STRING = "$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION";

##################################################
# Data                                           #
##################################################
my (%CPU, %OPERATING_SYSTEM, %COMPILER, %MODULES);

my @DOCS = (
   'api.pdf', 'tutorial.pdf', 'fips140.pdf',
   'api.tex', 'tutorial.tex', 'fips140.tex',
   'credits.txt', 'info.txt', 'license.txt', 'log.txt',
   'thanks.txt', 'todo.txt', 'pgpkeys.asc');

my %MODULE_SETS =
    (
     'unix' => [ 'alloc_mmap', 'es_egd', 'es_ftw', 'es_unix', 'fd_unix',
                 'tm_unix' ],
     'beos' => [ 'es_beos', 'es_unix', 'fd_unix', 'tm_unix' ],
     'win32' => ['es_capi', 'es_win32', 'mux_win32', 'tm_win32' ],
     );

my $TRACING = 0;

##################################################
# Run main() and Quit                            #
##################################################
main();
exit;

##################################################
# Main Driver                                    #
##################################################
sub main {
    my $config = {};

    my $base_dir = where_am_i();

    $$config{'base-dir'} = $base_dir;
    $$config{'config-dir'} = File::Spec->catdir($base_dir, 'misc', 'config');
    $$config{'mods-dir'} = File::Spec->catdir($base_dir, 'modules');
    $$config{'src-dir'} = File::Spec->catdir($base_dir, 'src');
    $$config{'include-dir'} = File::Spec->catdir($base_dir, 'include');
    $$config{'checks-dir'} = File::Spec->catdir($base_dir, 'checks');
    $$config{'doc-dir'} = File::Spec->catdir($base_dir, 'doc');

    %CPU = read_info_files($config, 'arch', \&get_arch_info);
    %OPERATING_SYSTEM = read_info_files($config, 'os', \&get_os_info);
    %COMPILER = read_info_files($config, 'cc', \&get_cc_info);
    %MODULES = read_module_files($config);

    add_to($config, {
        'version_major' => $MAJOR_VERSION,
        'version_minor' => $MINOR_VERSION,
        'version_patch' => $PATCH_VERSION,
        'version'       => $VERSION_STRING,
        });

    my ($target, $module_list) = get_options($config);

    my $default_value_is = sub {
        my ($var, $val) = @_;
        $$config{$var} = $val if not defined($$config{$var});
    };

    &$default_value_is('gcc_bug', 0);
    &$default_value_is('autoconfig', 1);
    &$default_value_is('debug', 0);
    &$default_value_is('shared', 'yes');
    &$default_value_is('build-dir', 'build');
    &$default_value_is('local_config', '');

    choose_target($config, $target);

    my $os = $$config{'os'};
    my $cc = $$config{'compiler'};

    &$default_value_is('prefix', os_info_for($os, 'install_root'));
    &$default_value_is('libdir', os_info_for($os, 'lib_dir'));
    &$default_value_is('docdir', os_info_for($os, 'doc_dir'));
    &$default_value_is('make_style', $COMPILER{$cc}{'makefile_style'});

    my @modules = choose_modules($config, $module_list);

    add_to($config, {
        'includedir'    => os_info_for($os, 'header_dir'),

        'build_lib'     => File::Spec->catdir($$config{'build-dir'}, 'lib'),
        'build_check'   => File::Spec->catdir($$config{'build-dir'}, 'checks'),
        'build_include' =>
            File::Spec->catdir($$config{'build-dir'}, 'include'),
        'build_include_botan' =>
            File::Spec->catdir($$config{'build-dir'}, 'include', 'botan'),

        'modules'       => [ @modules ],
        'mp_bits'       => find_mp_bits(@modules),
        'mod_libs'      => [ using_libs($os, @modules) ],

        'sources'       => {
            map_to($$config{'src-dir'}, dir_list($$config{'src-dir'}))
            },

        'includes'      => {
            map_to($$config{'include-dir'}, dir_list($$config{'include-dir'}))
            },

        'check_src'     => {
            map_to($$config{'checks-dir'},
                   grep { $_ ne 'keys' and !m@\.(dat|h)$@ }
                      dir_list($$config{'checks-dir'}))
            }
        });

    load_modules($config);

    mkdirs($$config{'build-dir'},
           $$config{'build_include'}, $$config{'build_include_botan'},
           $$config{'build_lib'}, $$config{'build_check'});

    write_pkg_config($config);

    process_template(File::Spec->catfile($$config{'config-dir'}, 'buildh.in'),
                     File::Spec->catfile($$config{'build-dir'}, 'build.h'),
                     $config);
    $$config{'includes'}{'build.h'} = $$config{'build-dir'};

    copy_include_files($config);

    generate_makefile($config);
}

sub where_am_i {
    my ($volume,$dir,$file) = File::Spec->splitpath($0);
    my $src_dir = File::Spec->catpath($volume, $dir, '');
    return $src_dir if $src_dir;
    return File::Spec->curdir();
}

##################################################
# Diagnostics                                    #
##################################################
sub with_diagnostic {
    my ($type, @args) = @_;

    my $args = join('', @args);
    my $str = "($type): ";
    while(length($str) < 14) { $str = ' ' . $str; }

    $str .= $args . "\n";
    return $str;
}

sub croak {
    die with_diagnostic('error', @_);
}

sub warning {
    warn with_diagnostic('note', @_);
}

sub autoconfig {
    print with_diagnostic('autoconfig', @_);
}

sub emit_help {
    print join('', @_);
    exit;
}

sub trace {
    return unless $TRACING;

    my (undef, undef, $line1) = caller(0);
    my (undef, undef, $line2, $func1) = caller(1);
    my (undef, undef, undef, $func2) = caller(2);

    $func1 =~ s/main:://;
    $func2 =~ s/main:://;

    warn with_diagnostic('trace', "at $func1:$line1 - ", @_);
}

##################################################
# Display Help and Quit                          #
##################################################
sub display_help {
   my $sets = join('|', sort keys %MODULE_SETS);

   my $helptxt = <<ENDOFHELP;
Usage: $0 [options] CC-OS-CPU

See doc/building.pdf for more information about this program.

Options:
  --prefix=PATH:       set the base installation directory
  --libdir=PATH:       install library files in \${prefix}/\${libdir}
  --docdir=PATH:       install documentation in \${prefix}/\${docdir}
  --build-dir=DIR:     setup the build in DIR
  --local-config=FILE: include the contents of FILE into build.h

  --modules=MODS:      add module(s) MODS to the library.
  --module-set=SET:    add a pre-specified set of modules ($sets)
  --module-info:       display some information about known modules

  --debug:             set compiler flags for debugging
  --disable-shared:    disable building shared libararies
  --noauto:            disable autoconfiguration
  --make-style=STYLE:  override the guess as to what type of makefile to use

You may use 'generic' for OS or CPU (useful if your OS or CPU isn't listed).

CPU can be a generic family name or a specific model name. Common aliases are
supported but not listed. Choosing a specific submodel will usually result in
code that will not run on earlier versions of that architecture.

ENDOFHELP

   my $listing = sub {
       my ($header, @list) = @_;

       return '' if (@list == 0);

       my ($output, $len) = ('', 0);

       my $append = sub {
          my ($to_append) = @_;
          $output .= $to_append;
          $len += length $to_append;
       };

       &$append($header . ': ');

       foreach my $name (sort @list) {
           next if $name eq 'defaults';
           if($len > 71) {
               $output .= "\n   ";
               $len = 3;
           }
           &$append($name . ' ');
       }
       return $output . "\n";
   };

    emit_help($helptxt,
              &$listing('CC', keys %COMPILER),
              &$listing('OS', keys %OPERATING_SYSTEM),
              &$listing('CPU', keys %CPU),
              &$listing('Modules', keys %MODULES));
}

##################################################
# Display Further Information about Modules      #
##################################################
sub display_module_info {

    my $info = '';
    foreach my $mod (sort keys %MODULES) {
        my $modinfo = $MODULES{$mod};
        my $fullname = $$modinfo{'realname'};

        while(length($mod) < 10) { $mod .= ' '; }
        $info .= "$mod - $fullname\n";
    }
    emit_help($info);
}

##################################################
#
##################################################
sub choose_target {
    my ($config, $target) = @_;

    if($target eq '' and $$config{'autoconfig'}) {
        $target = guess_triple();
        autoconfig("Guessing your system config is $target");
    }

    my ($cc,$os,$submodel) = split(/-/,$target,3);

    display_help()
        unless(defined($cc) and defined($os) and defined($submodel));

    croak("Compiler $cc isn't known (try --help)")
        unless defined($COMPILER{$cc});

    my %ccinfo = %{$COMPILER{$cc}};

    $os = os_alias($os);
    croak("OS $os isn't known (try --help)") unless
        ($os eq 'generic' or defined($OPERATING_SYSTEM{$os}));

    my $arch = undef;
    ($arch, $submodel) = figure_out_arch($submodel);

    croak(realname($os), " doesn't run on $arch ($submodel)")
        unless($arch eq 'generic' or $os eq 'generic' or
               in_array($arch, $OPERATING_SYSTEM{$os}{'arch'}));

    croak(realname($cc), " doesn't run on $arch ($submodel)")
        unless($arch eq 'generic' or
               (in_array($arch, $ccinfo{'arch'})));

    croak(realname($cc), " doesn't run on ", realname($os))
        unless($os eq 'generic' or (in_array($os, $ccinfo{'os'})));

    # hacks
    if($cc eq 'gcc') {
        $ccinfo{'binary_name'} = 'c++' if($os eq 'darwin');

        if($$config{'gcc_bug'} != 1) {
            my $binary = $ccinfo{'binary_name'};

            my $gcc_version = `$binary -v 2>&1`;

            $gcc_version = '' if not defined $gcc_version;

            # Some versions of GCC are a little buggy dealing with
            # long long in C++. The last check is because on Cygwin
            # (at least for me) gcc_version doesn't get the output,
            # maybe something to do with the stderr redirection? If
            # it's Cygwin and we didn't get output, assume it's a
            # buggy GCC. There is no reduction in code quality so even
            # if we're wrong it's OK.

            if(($gcc_version =~ /4\.[01]/) || ($gcc_version =~ /3\.[34]/) ||
               ($gcc_version =~ /2\.95\.[0-4]/) ||
               ($gcc_version eq '' && $^O eq 'cygwin'))
            {
                warning('Enabling -fpermissive to work around ',
                        'possible GCC bug');

                $$config{'gcc_bug'} = 1;
            }
            if($gcc_version =~ /2\.95\.[0-4]/)
            {
                warning('GCC 2.95.x issues many spurious warnings');
            }
        }
    }

    add_to($config, {
        'compiler'      => $cc,
        'os'            => $os,
        'arch'          => $arch,
        'submodel'      => $submodel,
    });
}

sub choose_modules {
    my ($config, $mod_str) = @_;

    my @modules = grep { $_ ne '' } split(/,/, $mod_str);

    if($$config{'autoconfig'})
    {
        foreach my $mod (guess_mods($config)) {

            autoconfig("Enabling module $mod")
                unless in_array($mod, \@modules);

            push @modules, $mod;
        }
    }

    # Uniqify @modules
    my %uniqed_mods = map_to(undef, @modules);
    @modules = sort keys %uniqed_mods;

    foreach (@modules) {
        croak("Module '$_' isn't known (try --help)")
            unless defined $MODULES{$_};
    }
    return @modules;
}

sub get_options {
    my ($config) = @_;

    my $save_option = sub {
        my ($opt, $val) = @_;
        $opt =~ s/-/_/g;
        $$config{$opt} = $val;
    };

    my $module_set = '';
    my @modules;
    exit 1 unless GetOptions(
               'help' => sub { display_help(); },
               'module-info' => sub { display_module_info(); },
               'version' => sub { emit_help("Botan $VERSION_STRING\n") },

               'prefix=s' => sub { &$save_option(@_); },
               'docdir=s' => sub { &$save_option(@_); },
               'libdir=s' => sub { &$save_option(@_); },
               'build-dir=s' => sub { &$save_option('build', $_[0]); },
               'local-config=s' =>
                  sub { &$save_option('local_config', slurp_file($_[1])); },

               'make-style=s' => sub { &$save_option(@_); },

               'modules=s' => \@modules,
               'module-set=s' => \$module_set,

               'debug' => sub { &$save_option($_[0], 1); },
               'disable-shared' => sub { $$config{'shared'} = 'no'; },
               'noauto' => sub { $$config{'autoconfig'} = 0; },
               'dumb-gcc|gcc295x' => sub { $$config{'gcc_bug'} = 1; }
               );

    croak("Module set $module_set isn't known (try --help)")
        if($module_set && !defined($MODULE_SETS{$module_set}));

    if($module_set) {
        foreach (@{ $MODULE_SETS{$module_set} }) { push @modules,$_; }
    }

    my $mod_str = join(',', @modules);

    return ('', $mod_str) if($#ARGV == -1);
    return ($ARGV[0], $mod_str) if($#ARGV == 0);
    display_help();
}

##################################################
# Functions to search the info tables            #
##################################################
sub figure_out_arch {
    my ($name) = @_;

    return ('generic', 'generic') if($name eq 'generic');

    my $submodel_alias = sub {
        my ($name,$info) = @_;

        my %info = %{$info};

        foreach my $submodel (@{$info{'submodels'}}) {
            return $submodel if($name eq $submodel);
        }

        return '' unless defined $info{'submodel_aliases'};
        my %sm_aliases = %{$info{'submodel_aliases'}};

        foreach my $alias (keys %sm_aliases) {
            my $official = $sm_aliases{$alias};
            return $official if($alias eq $name);
        }
        return '';
    };

    my $find_arch = sub {
        my $name = $_[0];

        foreach my $arch (keys %CPU) {
            my %info = %{$CPU{$arch}};

            return $arch if($name eq $arch);

            foreach my $alias (@{$info{'aliases'}}) {
                return $arch if($name eq $alias);
            }

            foreach my $submodel (@{$info{'submodels'}}) {
                return $arch if($name eq $submodel);
            }

            foreach my $submodel (keys %{$info{'submodel_aliases'}}) {
                return $arch if($name eq $submodel);
            }
        }
        return undef;
    };

    my $arch = &$find_arch($name);
    croak("Arch type $name isn't known (try --help)") unless defined $arch;
    trace("mapped name '$name' to arch '$arch'");

    my %archinfo = %{ $CPU{$arch} };

    my $submodel = &$submodel_alias($name, \%archinfo);

    if($submodel eq '') {
        $submodel = $archinfo{'default_submodel'};

        warning("Using $submodel as default type for family ", realname($arch))
           if($submodel ne $arch);
    }

    trace("mapped name '$name' to submodel '$submodel'");

    croak("Couldn't figure out arch type of $name")
        unless defined($arch) and defined($submodel);

    return ($arch,$submodel);
}

sub os_alias {
    my $name = $_[0];

    foreach my $os (keys %OPERATING_SYSTEM) {
        foreach my $alias (@{$OPERATING_SYSTEM{$os}{'aliases'}}) {
            if($alias eq $name) {
                trace("os_alias($name) -> $os");
                return $os;
                }
        }
    }

    return $name;
}

sub os_info_for {
    my ($os,$what) = @_;

    croak('os_info_for called with an os of defaults (internal problem)')
        if($os eq 'defaults');

    my $result = '';

    if(defined($OPERATING_SYSTEM{$os})) {
        my %osinfo = %{$OPERATING_SYSTEM{$os}};
        $result = $osinfo{$what};
    }

    if(!defined($result) or $result eq '') {
        $result = $OPERATING_SYSTEM{'defaults'}{$what};
    }

    croak("os_info_for: No info for $what on $os") unless defined $result;

    return $result;
}

sub my_compiler {
    my ($config) = @_;
    my $cc = $$config{'compiler'};

    croak('my_compiler called, but no compiler set in config')
        unless defined $cc and $cc ne '';

    croak("unknown compiler $cc") unless defined $COMPILER{$cc};

    return %{$COMPILER{$cc}};
}

sub mach_opt {
    my ($config) = @_;

    my %ccinfo = my_compiler($config);

    # Nothing we can do in that case
    return '' unless $ccinfo{'mach_opt_flags'};

    my $submodel = $$config{'submodel'};
    my $arch = $$config{'arch'};
    if(defined($ccinfo{'mach_opt_flags'}{$submodel}))
    {
        return $ccinfo{'mach_opt_flags'}{$submodel};
    }
    elsif(defined($ccinfo{'mach_opt_flags'}{$arch})) {
        my $mach_opt_flags = $ccinfo{'mach_opt_flags'}{$arch};
        my $processed_modelname = $submodel;

        my $remove = '';
        if(defined($ccinfo{'mach_opt_re'}) and
           defined($ccinfo{'mach_opt_re'}{$arch})) {
            $remove = $ccinfo{'mach_opt_re'}{$arch};
        }

        $processed_modelname =~ s/$remove//;
        $mach_opt_flags =~ s/SUBMODEL/$processed_modelname/g;
        return $mach_opt_flags;
    }
    return '';
}

##################################################
#                                                #
##################################################
sub using_libs {
   my ($os,@using) = @_;
   my %libs;

   foreach my $mod (@using) {
      my %MOD_LIBS = %{ $MODULES{$mod}{'libs'} };
      foreach my $mod_os (keys %MOD_LIBS) {
          next if($mod_os =~ /^all!$os$/);
          next if($mod_os =~ /^all!$os,/);
          next if($mod_os =~ /^all!.*,${os}$/);
          next if($mod_os =~ /^all!.*,$os,.*/);
          next unless($mod_os eq $os or ($mod_os =~ /^all.*/));
          my @liblist = split(/,/, $MOD_LIBS{$mod_os});
          foreach my $lib (@liblist) { $libs{$lib} = 1; }
          }
      }

   return sort keys %libs;
   }

sub libs {
    my ($prefix,$suffix,@libs) = @_;
    my $output = '';
    foreach my $lib (@libs) {
        $output .= ' ' if($output ne '');
        $output .= $prefix . $lib . $suffix;
    }
    return $output;
}

##################################################
# Path and file manipulation utilities           #
##################################################
sub portable_symlink {
   my ($from, $to_dir, $to_fname) = @_;

   my $can_symlink = 0;
   my $can_link = 0;

   unless($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'cygwin') {
       $can_symlink = eval { symlink("",""); 1 };
       $can_link = eval { link("",""); 1 };
   }

   chdir $to_dir or croak("Can't chdir to $to_dir ($!)");

   if($can_symlink) {
       symlink $from, $to_fname or
           croak("Can't symlink $from to $to_fname ($!)");
   }
   elsif($can_link) {
       link $from, $to_fname or
           croak("Can't link $from to $to_fname ($!)");
   }
   else {
       copy ($from, $to_fname) or
           croak("Can't copy $from to $to_fname ($!)");
   }

   my $go_up = File::Spec->splitdir($to_dir);
   for(my $j = 0; $j != $go_up; $j++) # return to where we were
   {
       chdir File::Spec->updir();
   }
}

sub copy_include_files {
    my ($config) = @_;

    my $include_dir = $$config{'build_include_botan'};

    trace('Copying to ', $include_dir);

    foreach my $file (dir_list($include_dir)) {
        my $path = File::Spec->catfile($include_dir, $file);
        unlink $path or croak("Could not unlink $path ($!)");
    }

   my $link_up = sub {
       my ($dir, $file) = @_;
       my $updir = File::Spec->updir();
       portable_symlink(File::Spec->catfile($updir, $updir, $updir,
                                            $dir, $file),
                        $include_dir, $file);
   };

    my $files = $$config{'includes'};

    foreach my $file (keys %$files) {
        &$link_up($$files{$file}, $file);
    }
}

sub dir_list {
    my ($dir) = @_;
    opendir(DIR, $dir) or croak("Couldn't read directory '$dir' ($!)");

    my @listing = grep { $_ ne File::Spec->curdir() and
                         $_ ne File::Spec->updir() } readdir DIR;

    closedir DIR;
    return @listing;
}

sub mkdirs {
    my (@dirs) = @_;
    foreach my $dir (@dirs) {
        next if( -e $dir and -d $dir ); # skip it if it's already there
        mkdir($dir, 0777) or
            croak("Could not create directory $dir ($!)");
    }
}

sub slurp_file {
    my $file = $_[0];

    return '' if(!defined($file) or $file eq '');

    croak("'$file': No such file") unless(-e $file);
    croak("'$file': Not a regular file") unless(-f $file);

    open FILE, "<$file" or croak("Couldn't read $file ($!)");

    my $output = '';
    while(<FILE>) { $output .= $_; }
    close FILE;

    return $output;
}

sub which
{
    my $file = $_[0];
    my @paths = split(/:/, $ENV{PATH});
    foreach my $path (@paths)
    {
        my $file_path = File::Spec->catfile($path, $file);
        return $file_path if(-e $file_path and -r $file_path);
    }
    return '';
}

# Return a hash mapping every var in a list to a constant value
sub map_to {
    my $var = shift;
    return map { $_ => $var } @_;
}

sub in_array {
    my($target, $array) = @_;
    return 0 unless defined($array);
    foreach (@$array) { return 1 if($_ eq $target); }
    return 0;
}

sub add_to {
    my ($to,$from) = @_;

    foreach my $key (keys %$from) {
        $$to{$key} = $$from{$key};
    }
}

##################################################
#                                                #
##################################################
sub find_mp_bits {
    my(@modules_list) = @_;
    my $mp_bits = 32; # default, good for most systems

    my $seen_mp_module = undef;

    foreach my $modname (@modules_list) {
        my %modinfo = %{ $MODULES{$modname} };
        if($modinfo{'mp_bits'}) {
            if(defined($seen_mp_module) and $modinfo{'mp_bits'} != $mp_bits) {
                croak('Inconsistent mp_bits requests from modules ',
                      $seen_mp_module, ' and ', $modname);
            }

            $seen_mp_module = $modname;
            $mp_bits = $modinfo{'mp_bits'};
        }
    }
    return $mp_bits;
}

##################################################
#                                                #
##################################################
sub realname {
    my $arg = $_[0];

    return $COMPILER{$arg}{'realname'}
       if defined $COMPILER{$arg};

    return $OPERATING_SYSTEM{$arg}{'realname'}
       if defined $OPERATING_SYSTEM{$arg};

    return $CPU{$arg}{'realname'}
       if defined $CPU{$arg};

    return $arg;
}

##################################################
#                                                #
##################################################
sub load_modules {
    my ($config) = @_;

    my @modules = @{$$config{'modules'}};

    foreach my $mod (@modules) {
        load_module($config, $mod);
    }

    my $gen_defines = sub {
        my $defines = '';

        my $arch = $$config{'arch'};
        if($arch ne 'generic') {
            $arch = uc $arch;
            $defines .= "#define BOTAN_TARGET_ARCH_IS_$arch\n";

            my $submodel = $$config{'submodel'};
            if($arch ne $submodel) {
                $submodel = uc $submodel;
                $submodel =~ s/-/_/g;
                $defines .= "#define BOTAN_TARGET_CPU_IS_$submodel\n";
            }
        }

        my @defarray;
        foreach my $mod (@modules) {
            my $defs = $MODULES{$mod}{'define'};
            next unless $defs;

            push @defarray, split(/,/, $defs);
        }
        foreach (sort @defarray) {
            die unless(defined $_ and $_ ne '');
            $defines .= "#define BOTAN_EXT_$_\n";
        }
        chomp($defines);
        return $defines;
    };

    $$config{'defines'} = &$gen_defines();
}

sub load_module {
    my ($config, $modname) = @_;

    my %module = %{$MODULES{$modname}};

    my $works_on = sub {
        my ($what, @lst) = @_;
        return 1 if not @lst; # empty list -> no restrictions
        return 1 if $what eq 'generic'; # trust the user
        return in_array($what, \@lst);
    };

    # Check to see if everything is OK WRT system requirements
    my $os = $$config{'os'};

    croak("Module '$modname' does not run on $os")
        unless(&$works_on($os, @{$module{'os'}}));

    my $arch = $$config{'arch'};
    my $sub = $$config{'submodel'};

    croak("Module '$modname' does not run on $arch/$sub")
        unless(&$works_on($arch, @{$module{'arch'}}) or
               &$works_on($sub, @{$module{'arch'}}));

    my $cc = $$config{'compiler'};

    croak("Module '$modname' does not work with $cc")
        unless(&$works_on($cc, @{$module{'cc'}}));

    my $handle_files = sub {
        my($lst, $func) = @_;
        return unless defined($lst);

        foreach (sort @$lst) {
            &$func($modname, $config, $_);
        }
    };

    &$handle_files($module{'ignore'},  \&ignore_file);
    &$handle_files($module{'add'},     \&add_file);
    &$handle_files($module{'replace'},
                   sub { ignore_file(@_); add_file(@_); });

    warning($modname, ': ', $module{'note'})
        if(defined($module{'note'}));
}

##################################################
#                                                #
##################################################
sub file_type {
    my ($config, $file) = @_;

    return ('sources', $$config{'src-dir'})
        if($file =~ /\.cpp$/ or $file =~ /\.S$/);
    return ('includes', $$config{'include-dir'})
        if($file =~ /\.h$/);

    croak('file_type() - don\'t know what sort of file ', $file, ' is');
}

sub add_file {
    my ($modname, $config, $file) = @_;

    check_for_file($config, $file, $modname, $modname);

    my $mod_dir = File::Spec->catdir($$config{'mods-dir'}, $modname);

    my $do_add_file = sub {
        my ($type) = @_;

        croak("File $file already added from ", $$config{$type}{$file})
            if(defined($$config{$type}{$file}));

        $$config{$type}{$file} = $mod_dir;
    };

    &$do_add_file(file_type($config, $file));
}

sub ignore_file {
    my ($modname, $config, $file) = @_;
    check_for_file($config, $file, undef, $modname);

    my $do_ignore_file = sub {
        my ($type, $ok_if_from) = @_;

        if(defined ($$config{$type}{$file})) {

            croak("$modname - File $file modified from ",
                  $$config{$type}{$file})
                if($$config{$type}{$file} ne $ok_if_from);

            delete $$config{$type}{$file};
        }
    };

    &$do_ignore_file(file_type($config, $file));
}

sub check_for_file {
   my ($config, $file, $added_from, $modname) = @_;

   my $full_path = sub {
       my ($file,$modname) = @_;

       return File::Spec->catfile($$config{'mods-dir'}, $modname, $file)
           if(defined($modname));

       my @typeinfo = file_type($config, $file);
       return File::Spec->catfile($typeinfo[1], $file);
   };

   $file = &$full_path($file, $added_from);

   croak("Module $modname requires that file $file exist. This error\n      ",
         'should never occur; please contact the maintainers with details.')
       unless(-e $file);
}

##################################################
#                                                #
##################################################
sub process_template {
    my ($in, $out, $config) = @_;

    trace("$in -> $out");

    my $contents = slurp_file($in);

    foreach my $name (keys %$config) {
        my $val = $$config{$name};

        croak("Undefined variable $name in $in") unless defined $val;

        $contents =~ s/@\{var:$name\}/$val/g;

        unless($val eq 'no' or $val eq 'false') {
            $contents =~ s/\@\{if:$name (.*)\}/$1/g;
            $contents =~ s/\@\{if:$name (.*) (.*)\}/$1/g;
        } else {
            $contents =~ s/\@\{if:$name (.*)\}//g;
            $contents =~ s/\@\{if:$name (.*) (.*)\}/$2/g;
        }
    }

    if($contents =~ /@\{var:([a-z_]*)\}/ or
       $contents =~ /@\{if:(.*) /) {
        croak("Unbound variable '$1' in $in");
    }

    open OUT, ">$out" or croak("Couldn't write $out ($!)");
    print OUT $contents;
    close OUT;
}

##################################################
#                                                #
##################################################
sub read_list {
    my ($line, $reader, $marker, $func) = @_;

    if($line =~ m@^<$marker>$@) {
        while(1) {
            $line = &$reader();
            last if($line =~ m@^</$marker>$@);
            &$func($line);
        }
    }
}

sub list_push {
    my ($listref) = @_;
    return sub { push @$listref, $_[0]; }
}

sub match_any_of {
    my ($line, $hash, $quoted, $any_of) = @_;

    $quoted = ($quoted eq 'quoted') ? 1 : 0;

    my @match_these = split(/:/, $any_of);

    foreach my $what (split(/:/, $any_of)) {
        $$hash{$what} = $1 if(not $quoted and $line =~ /^$what (.*)/);
        $$hash{$what} = $1 if($quoted and $line =~ /^$what \"(.*)\"/);
    }
}

##################################################
#                                                #
##################################################
sub make_reader {
    my $filename = $_[0];

    croak("make_reader(): Arg was undef") if not defined $filename;

    open FILE, "<$filename" or
        croak("Couldn't read $filename ($!)");

    return sub {
        my $line = '';
        while(1) {
            my $line = <FILE>;
            last unless defined($line);

            chomp($line);
            $line =~ s/#.*//;
            $line =~ s/^\s*//;
            $line =~ s/\s*$//;
            $line =~ s/\s\s*/ /;
            $line =~ s/\t/ /;
            return $line if $line ne '';
        }
        close FILE;
        return undef;
    }
}

##################################################
#                                                #
##################################################
sub read_info_files {
    my ($config, $dir, $func) = @_;

    $dir = File::Spec->catdir($$config{'config-dir'}, $dir);

    my %allinfo;
    foreach my $file (dir_list($dir)) {
        my $fullpath = File::Spec->catfile($dir, $file);

        trace("reading $fullpath");
        %{$allinfo{$file}} = &$func($file, $fullpath);
    }

    return %allinfo;
}

sub read_module_files {
    my ($config) = @_;

    my $mod_dir = $$config{'mods-dir'};

    my %allinfo;
    foreach my $dir (dir_list($mod_dir)) {
        my $modfile = File::Spec->catfile($mod_dir, $dir, 'modinfo.txt');

        trace("reading $modfile");
        %{$allinfo{$dir}} = get_module_info($dir, $modfile);
    }

    return %allinfo;
}

##################################################
#                                                #
##################################################
sub get_module_info {
   my ($name, $file) = @_;
   my $reader = make_reader($file);

   my %info;
   $info{'name'} = $name;
   $info{'external_libs'} = 0;
   $info{'libs'} = {};

   while($_ = &$reader()) {
       match_any_of($_, \%info, 'quoted', 'realname:note');
       match_any_of($_, \%info, 'unquoted', 'define:mp_bits');

       $info{'external_libs'} = 1 if(/^uses_external_libs/);

       read_list($_, $reader, 'arch', list_push(\@{$info{'arch'}}));
       read_list($_, $reader, 'cc', list_push(\@{$info{'cc'}}));
       read_list($_, $reader, 'os', list_push(\@{$info{'os'}}));
       read_list($_, $reader, 'add', list_push(\@{$info{'add'}}));
       read_list($_, $reader, 'replace', list_push(\@{$info{'replace'}}));
       read_list($_, $reader, 'ignore', list_push(\@{$info{'ignore'}}));

       read_list($_, $reader, 'libs',
                 sub {
                     my $line = $_[0];
                     $line =~ m/^([\w!,]*) -> ([\w,-]*)$/;
                     $info{'libs'}{$1} = $2;
                 });

       if(/^require_version /) {
           if(/^require_version (\d+)\.(\d+)\.(\d+)$/) {
               my $version = "$1.$2.$3";
               my $needed_version = 100*$1 + 10*$2 + $3;

               my $have_version =
                   100*$MAJOR_VERSION + 10*$MINOR_VERSION + $PATCH_VERSION;

               if($needed_version > $have_version) {
                   warning("Module $name needs v$version; disabling");
                   return ();
               }
           }
           else {
               croak("In module $name, bad version requirement '$_'");
           }
       }
   }

   return %info;
}

##################################################
#                                                #
##################################################
sub get_arch_info {
    my ($name,$file) = @_;
    my $reader = make_reader($file);

    my %info;
    $info{'name'} = $name;

    while($_ = &$reader()) {
        match_any_of($_, \%info, 'quoted', 'realname');
        match_any_of($_, \%info, 'unquoted', 'default_submodel');

        read_list($_, $reader, 'aliases', list_push(\@{$info{'aliases'}}));
        read_list($_, $reader, 'submodels', list_push(\@{$info{'submodels'}}));

        read_list($_, $reader, 'submodel_aliases',
                  sub {
                      my $line = $_[0];
                      $line =~ m/^(\S*) -> (\S*)$/;
                      $info{'submodel_aliases'}{$1} = $2;
                  });
    }
    return %info;
}

##################################################
#                                                #
##################################################
sub get_os_info {
    my ($name,$file) = @_;
    my $reader = make_reader($file);

    my %info;
    $info{'name'} = $name;

    while($_ = &$reader()) {
        match_any_of($_, \%info, 'quoted', 'realname:ar_command');

        match_any_of($_, \%info, 'unquoted',
                   'os_type:obj_suffix:so_suffix:static_suffix:' .
                   'install_root:header_dir:lib_dir:doc_dir:' .
                   'install_user:install_group:ar_needs_ranlib:' .
                   'install_cmd_data:install_cmd_exec');

        read_list($_, $reader, 'aliases', list_push(\@{$info{'aliases'}}));
        read_list($_, $reader, 'arch', list_push(\@{$info{'arch'}}));

        read_list($_, $reader, 'supports_shared',
                  list_push(\@{$info{'supports_shared'}}));
    }
    return %info;
}

##################################################
#                                                #
##################################################
sub get_cc_info {
    my ($name,$file) = @_;
    my $reader = make_reader($file);

    my %info;
    $info{'name'} = $name;

    while($_ = &$reader()) {
        match_any_of($_, \%info, 'quoted',
                   'realname:binary_name:' .
                   'compile_option:output_to_option:add_include_dir_option:' .