#!/usr/bin/env perl
#-----------------------------------------------------------------------------------------------
#
# configure
#
#
# This utility allows the CLM user to specify compile-time configuration
# options via a commandline interface.  The output from configure is a
# Makefile and a cache file that contains all configuration parameters
# required to produce the Makefile.  A subsequent invocation of configure
# can use the cache file as input (via the -defaults argument) to reproduce
# the CLM configuration contained in it.  Note that when a cache file is
# used to set default values only the model parameters are used.  The
# parameters that are platform dependent (e.g., compiler options, library
# locations, etc) are ignored.
#
# As the build time configurable options of CLM are changed, this script
# must also be changed.  Thus configure is maintained under revision
# control in the CLM source tree and it is assumed that only the version of
# configure in the source tree will be used to build CLM.  Thus we assume
# that the root of the source tree can be derived from the location of this
# script.
#
#-----------------------------------------------------------------------------------------------

use strict;
#use warnings;
#use diagnostics;

use Cwd;
use English;
use Getopt::Long;
use IO::File;
use IO::Handle;
use File::Copy;

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

sub usage {
    die <<EOF;
SYNOPSIS
     configure [options]

     Configure CLM in preparation to be built.
OPTIONS
     User supplied values are denoted in angle brackets (<>).  Any value that contains
     white-space must be quoted.  Long option names may be supplied with either single
     or double leading dashes.  A consequence of this is that single letter options may
     NOT be bundled.

     -bgc <name>            Build CLM with BGC package [ none | cn | cndv ] 
                            (default is none).
     -cache <file>          Name of output cache file (default: config_cache.xml).
     -cachedir <file>       Name of directory where output cache file is written 
                            (default: CLM build directory).
     -clm_root <dir>        Root directory of clm source code 
                            (default: directory above location of this script)
     -cppdefs <string>      A string of user specified CPP defines.  Appended to
                            Makefile defaults.  e.g. -cppdefs '-DVAR1 -DVAR2'
     -crop <name>           Toggle for prognostic crop model. [on | off] (default is off) 
                            (can ONLY be turned on when BGC type is CN or CNDV)
     -comp_intf <name>      Component interface to use (ESMF or MCT) (default MCT)
     -defaults <file>       Specify full path to a configuration file which will be used 
                            to supply defaults instead of the defaults in bld/config_files.
                            This file is used to specify model configuration parameters only.
                            Parameters relating to the build which are system dependent will
                            be ignored.
     -help [or -h]          Print usage to STDOUT.
     -nofire                Turn off wildfires for BGC setting of CN 
                            (default includes fire for CN)
     -noio                  Turn history output completely off (typically for testing).
     -phys <name>           Value of clm4_0, clm4_5, or clm5_0 (default is clm4_0)   
     -silent [or -s]        Turns on silent mode - only fatal messages issued.
     -sitespf_pt <name>     Setup for the given site specific single-point resolution.
     -snicar_frc <name>     Turn on SNICAR radiative forcing calculation. [on | off] 
                            (default is off)
     -spinup <name>         CLM 4.0 Only. For CLM 4.5, spinup is controlled from build-namelist.
                            Turn on given spinup mode for BGC setting of CN		  (level)
                              AD            Turn on Accelerated Decomposition from	      (2)
                                            bare-soil
                              exit          Jump directly from AD spinup to normal mode	      (1)
                              normal        Normal decomposition ("final spinup mode")	      (0)
                                            (default)
                            The recommended sequence is 2-1-0
     -usr_src <dir1>[,<dir2>[,<dir3>[...]]]
                            Directories containing user source code.
     -verbose [or -v]       Turn on verbose echoing of settings made by configure.
     -version               Echo the SVN tag name used to check out this CLM distribution.
EOF
}

#-----------------------------------------------------------------------------------------------
# Setting autoflush (an IO::Handle method) on STDOUT helps in debugging.  It forces the test
# descriptions to be printed to STDOUT before the error messages start.

*STDOUT->autoflush();                  

#-----------------------------------------------------------------------------------------------
# Set the directory that contains the CLM configuration scripts.  If the configure command was
# issued using a relative or absolute path, that path is in $ProgDir.  Otherwise assume the
# command was issued from the current working directory.

(my $ProgName = $0) =~ s!(.*)/!!;      # name of this script
my $ProgDir = $1;                      # name of directory containing this script -- may be a
                                       # relative or absolute path, or null if the script is in
                                       # the user's PATH
my $cwd = getcwd();                    # current working directory
my $cfgdir;                            # absolute pathname of directory that contains this script
if ($ProgDir) { 
    $cfgdir = absolute_path($ProgDir);
} else {
    $cfgdir = $cwd;
}

#-----------------------------------------------------------------------------------------------
# Save commandline
my $commandline = "$cfgdir/configure @ARGV";

#-----------------------------------------------------------------------------------------------
# Parse command-line options.
my %opts = (
	    cache       => "config_cache.xml",
            phys        => "clm4_0",
            nofire      => undef,
            noio        => undef,
            clm_root    => undef,
            spinup      => "normal",
	    );
GetOptions(
    "spinup=s"                  => \$opts{'spinup'}, 
    "bgc=s"                     => \$opts{'bgc'}, 
    "cache=s"                   => \$opts{'cache'},
    "cachedir=s"                => \$opts{'cachedir'},
    "snicar_frc=s"              => \$opts{'snicar_frc'},
    "clm_root=s"                => \$opts{'clm_root'},
    "cppdefs=s"                 => \$opts{'cppdefs'},
    "comp_intf=s"               => \$opts{'comp_intf'},
    "defaults=s"                => \$opts{'defaults'},
    "clm4me=s"                  => \$opts{'clm4me'},
    "h|help"                    => \$opts{'help'},
    "nofire"                    => \$opts{'nofire'},
    "noio"                      => \$opts{'noio'},
    "phys=s"                    => \$opts{'phys'},
    "snicar_frc=s"              => \$opts{'snicar_frc'}, 
    "s|silent"                  => \$opts{'silent'},
    "sitespf_pt=s"              => \$opts{'sitespf_pt'},
    "usr_src=s"                 => \$opts{'usr_src'},
    "v|verbose"                 => \$opts{'verbose'},
    "version"                   => \$opts{'version'},
    "crop=s"                    => \$opts{'crop'}, 
)  or usage();

# Give usage message.
usage() if $opts{'help'};

# Echo version info.
version($cfgdir) if $opts{'version'};    

# Check for unparsed arguments
if (@ARGV) {
    print "ERROR: unrecognized arguments: @ARGV\n";
    usage();
}

# Define 3 print levels:
# 0 - only issue fatal error messages
# 1 - only informs what files are created (default)
# 2 - verbose
my $print = 1;
if ($opts{'silent'})  { $print = 0; }
if ($opts{'verbose'}) { $print = 2; }
my $eol = "\n";

my %cfg = ();           # build configuration

#-----------------------------------------------------------------------------------------------
# Make sure we can find required perl modules and configuration files.
# Look for them in the directory that contains the configure script.

# The XML::Lite module is required to parse the XML configuration files.
(-f "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib/XML/Lite.pm")  or  die <<"EOF";
** Cannot find perl module \"XML/Lite.pm\" in directory 
    \"$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib\" **
EOF

# The Build::Config module provides utilities to store and manipulate the configuration.
(-f "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib/Build/Config.pm")  or  die <<"EOF";
** Cannot find perl module \"Build/Config.pm\" in directory 
    \"$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib\" **
EOF
#-----------------------------------------------------------------------------------------------
# Add $cfgdir/perl5lib to the list of paths that Perl searches for modules
my $casecfgdir = "$cfgdir/../../../../scripts/ccsm_utils/Case.template";
my @dirs = (  $cfgdir, "$cfgdir/../../../../scripts/ccsm_utils/Tools/perl5lib", $casecfgdir);
unshift @INC, @dirs;
require XML::Lite;
require Build::Config;
require config_files::clm_phys_vers;

# Get the physics version
my $phys = config_files::clm_phys_vers->new($opts{'phys'});

# Check for the physics specific configuration definition file.
my $phys_string = $phys->as_filename();

my $config_def_file = "config_definition_$phys_string.xml";
(-f "$cfgdir/config_files/$config_def_file")  or  die <<"EOF";
** Cannot find configuration definition file \"$config_def_file\" in directory 
    \"$cfgdir/config_files\" **
EOF

# The configuration defaults file modifies the generic defaults in the configuration
# definition file.  Note that the -defaults option has precedence over all other options.
my $config_defaults_file;
my $std_config_defaults_file = "$cfgdir/config_files/config_defaults.xml";
if    ($opts{'defaults'})           {
    $config_defaults_file = $opts{'defaults'};
} elsif (defined($opts{'sitespf_pt'}) and $phys->as_long() == $phys->as_long( "clm4_0" ) ) {
    $config_defaults_file = "$cfgdir/config_files/config_defaults_$opts{'sitespf_pt'}.xml";
    if ( ! -f $config_defaults_file ) {
       $config_defaults_file = "$std_config_defaults_file";
    }
} else {
    $config_defaults_file = "$std_config_defaults_file";
}
(-f "$config_defaults_file")  or  die <<"EOF";
** Cannot find configuration defaults file \"$config_defaults_file\" **
EOF

if ($print>=2) { print "Setting CLM configuration script directory to $cfgdir$eol"; }
if ($print>=2) { print "Using configuration defaults file $config_defaults_file$eol"; }

# Initialize the configuration.  The $config_def_file provides the definition of a CLM
# configuration, and the $config_defaults_file provides default values for a specific CLM
# configuration.   $cfg_ref is a reference to the new configuration object.
my $cfg_ref = Build::Config->new("$cfgdir/config_files/$config_def_file", 
				 "$config_defaults_file");

#-----------------------------------------------------------------------------------------------
# CLM root directory.  
my $clm_root;

if ( ! defined($opts{'clm_root'} ) ) {
  $clm_root = absolute_path("$cfgdir/..");
} else {
  $clm_root = $opts{'clm_root'};
}

if ( &is_valid_directory( "$clm_root/src", allowEnv=>1 ) ) {
    $cfg_ref->set('clm_root', $clm_root);
} else {
    die <<"EOF";
** Invalid CLM root directory: $clm_root
** 
** The CLM root directory must contain the subdirectory /src/.
** clm_root can be entered on the command line or it will be derived
** from the location of this script.
EOF
}

if ($print>=2) { print "Setting CLM root directory to $clm_root$eol"; }

#-----------------------------------------------------------------------------------------------
# CLM build directory is current directory
my $clm_bld = `pwd`;
chomp( $clm_bld );

# Make sure directory is valid
if ( ! &is_valid_directory( $clm_bld ) and ! mkdirp($clm_bld)) {
    die <<"EOF";
** Could not create the specified CLM build directory: $clm_bld
EOF
}

if ($print>=2) { print "Setting CLM build directory to $clm_bld$eol"; }

#-----------------------------------------------------------------------------------------------
# User source directories.
my $usr_src = '';
if (defined $opts{'usr_src'}) {
    my @dirs = split ',', $opts{'usr_src'};
    my @adirs;
    while ( my $dir = shift @dirs ) {
	if (&is_valid_directory( "$dir", allowEnv=>1 ) ) {
	    push @adirs, $dir;
	} else {
	    die "** User source directory does not exist: $dir\n";
	}
    }
    $usr_src = join ',', @adirs;
    $cfg_ref->set('usr_src', $usr_src);
}

if ($print>=2) { print "Setting user source directories to $usr_src$eol"; }

#-----------------------------------------------------------------------------------------------
# configuration cache directory and file.
my $config_cache_dir;
my $config_cache_file;
if (defined $opts{'cachedir'}) {
    $config_cache_dir = absolute_path($opts{'cachedir'});
}
else {
    $config_cache_dir = $clm_bld;
}

if (&is_valid_directory( $config_cache_dir, allowEnv=>1 ) or mkdirp($config_cache_dir)) {
    $config_cache_file = "$config_cache_dir/$opts{'cache'}";
} else {
    die <<"EOF";
** Could not create the specified directory for configuration cache file: $config_cache_dir
EOF
}

if ($print>=2) { print "The configuration cache file will be created in $config_cache_file$eol"; }


#-----------------------------------------------------------------------------------------------
# physics

$cfg_ref->set('phys', $opts{'phys'});
my $phys_string = $phys->as_string();
if ($print>=2) { 
   if( defined($opts{'phys'}) ) { 
      print "Using version $phys_string physics.$eol"; 
   }
}

#-----------------------------------------------------------------------------------------------
# supported single point configurations
my $sitespf_pt = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if( defined($opts{'sitespf_pt'}) ) {
    $cfg_ref->set('sitespf_pt', $opts{'sitespf_pt'});
  }
  $sitespf_pt = $cfg_ref->get('sitespf_pt');
  if ($print>=2) { 
    if( defined($opts{'sitespf_pt'}) ) { 
      print "Using $sitespf_pt for supported single point configuration.$eol"; 
    }
  }
}

#-----------------------------------------------------------------------------------------------
# NOIO option
my $noio = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'noio'}) {
    $cfg_ref->set('noio', "on" );
  }
  $noio = $cfg_ref->get('noio');
  if ($print>=2) { 
    if ( $noio eq "on") { print "ALL history output is turned OFF.$eol";       }
  }
}
#-----------------------------------------------------------------------------------------------
# BGC option
my $bgc_mode = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'bgc'}) {
    $cfg_ref->set('bgc', $opts{'bgc'});
  }
  $bgc_mode = $cfg_ref->get('bgc');
  if ($print>=2) { print "Using $bgc_mode for bgc.$eol"; }
  if ( $bgc_mode eq "casa" ) {
    print "Warning:: bgc=casa is NOT validated / scientifically supported.$eol";
  }
}

# NOFIRE option -- currently only in bgc=CN
my $nofire = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'nofire'}) {
    $cfg_ref->set('nofire', "on" );
  }
  $nofire = $cfg_ref->get('nofire');
  if ( ($nofire eq "on") && ($bgc_mode ne "cn") ) {
    die <<"EOF";
** Cannot turn nofire mode on -- without cn for bgc mode** 
EOF
  }
  if ($print>=2 && $bgc_mode =~ /^cn/ ) { 
    if ( $nofire eq "off") { print "Wildfires are active as normal.$eol"; }
    else                   { print "Wildfires are turned off.$eol";       }
  }
}

#-----------------------------------------------------------------------------------------------
# SPINUP option for BGC/CN mode only
my $spinup = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'spinup'}) {
    $cfg_ref->set('spinup', $opts{'spinup'});
  }
  $spinup = $cfg_ref->get('spinup');
  if ( ($spinup ne "normal" ) && ($bgc_mode ne "cn") ) {
    die <<"EOF";
** Cannot turn spinup mode on -- without cn for bgc mode** 
** 
** Set the bgc mode by the following means from highest to lowest precedence:
** * by the command-line option -bgc cn
** * by a default configuration file, specified by -defaults 
EOF
  }
  if ($print>=2) { print "Using $spinup for spinup for cn mode.$eol"; }
} else {
  if ($opts{'spinup'} ne "normal") {
    die <<"EOF";
** Spinup mode can only be controlled with configure for CLM 4.0.
** For CLM 4.5 use the bgc_spinup option to build-namelist
EOF
  }
}

#-----------------------------------------------------------------------------------------------
# comp_intf option
if (defined $opts{'comp_intf'}) {
    $cfg_ref->set('comp_intf', $opts{'comp_intf'});
}
my $comp_intf = $cfg_ref->get('comp_intf');
if ($print>=2) { print "Using $comp_intf for comp_intf.$eol"; }


#-----------------------------------------------------------------------------------------------
# CROP option
my $crpmode = undef;
my $crop = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'crop'}) {
    $cfg_ref->set('crop', $opts{'crop'});
  }
  $crpmode = "nocrop";
  $crop = $cfg_ref->get('crop');
  if ( $crop eq "on" ) {
    $crpmode = "crop";
  }
  if ( ($crop eq "on" ) && ($bgc_mode ne "cn") && ($bgc_mode ne "cndv") ) {
    die <<"EOF";
** Cannot turn crop mode on -- without some form of cn for bgc mode** 
** 
** Set the bgc mode by the following means from highest to lowest precedence:
** * by the command-line options -bgc cn
** * by a default configuration file, specified by -defaults 
EOF
  }
}

#-----------------------------------------------------------------------------------------------
# MAXPFT option

my %maxpatchpft;
my $maxpft = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  $maxpatchpft{'crop'}   = 21;
  $maxpatchpft{'nocrop'} = 17;

  $cfg_ref->set('maxpft', $maxpatchpft{$crpmode} );
  $maxpft = $cfg_ref->get('maxpft');
  if ( (($bgc_mode eq "cn") || ($bgc_mode eq "cndv")) && ($maxpft != $maxpatchpft{$crpmode}) ) {
    die <<"EOF";
** For CN or CNDV BGC mode you MUST set max patch PFT's to $maxpatchpft{$crpmode}
** 
** When the crop model is on then it must be set to $maxpatchpft{'crop'} otherwise to $maxpatchpft{'nocrop'}
** Set the bgc mode, crop and maxpft by the following means from highest to lowest precedence:
** * by the command-line options -bgc, -crop and -maxpft
** * by a default configuration file, specified by -defaults 
**
EOF
  }
  if ( $maxpft > $maxpatchpft{$crpmode} ) {
    die <<"EOF";
** Max patch PFT's can NOT exceed $maxpatchpft{$crpmode}
** 
** Set maxpft by the following means from highest to lowest precedence:
** * by the command-line options -maxpft
** * by a default configuration file, specified by -defaults 
**
EOF
  }
  if ( $maxpft != $maxpatchpft{$crpmode} ) {
    print "Warning:: running with maxpft NOT equal to $maxpatchpft{$crpmode} is " .
      "NOT validated / scientifically supported.$eol";
  }
  if ($print>=2) { print "Using $maxpft for maxpft.$eol"; }
}
#-----------------------------------------------------------------------------------------------
# SNICAR_FRC option
my $snicar_frc = undef;
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  if (defined $opts{'snicar_frc'}) {
    $cfg_ref->set('snicar_frc', $opts{'snicar_frc'});
  }
  $snicar_frc = $cfg_ref->get('snicar_frc');
  if ($print>=2) { print "Using $snicar_frc for snicar_frc.$eol"; }
}

#-----------------------------------------------------------------------------------------------
# Makefile configuration #######################################################################
#-----------------------------------------------------------------------------------------------

#-----------------------------------------------------------------------------------------------
# Name of CLM executable.
my $clm_exe = "clm";

if ($print>=2) { print "Name of CLM executable: $clm_exe.$eol"; }

#-----------------------------------------------------------------------------------------------
# For the CPP tokens, start with the defaults (from defaults file) and append the specifications
# from the commandline.  That way the user can override defaults since the commandline versions
# occur last.
my $usr_cppdefs = $cfg_ref->get('cppdefs');
if (defined $opts{'cppdefs'}) {
    $usr_cppdefs .= " $opts{'cppdefs'}";
    print "Warning:: running with user defined cppdefs is NOT validated / " . 
          "scientifically supported.$eol";
}
$cfg_ref->set('cppdefs', $usr_cppdefs);

if ($usr_cppdefs and $print>=2) { print "Default and user CPP definitions: \'$usr_cppdefs\'$eol";}

# The following CPP macro definitions are used to implement the compile-time options.  They are
# determined by the configuration parameters that have been set above.  They will be appended to
# the CPP definitions that were explicitly set in the defaults file or by the user on the commandline.
my $cfg_cppdefs = '';
if ($phys->as_long() == $phys->as_long("clm4_0") ) {
  $cfg_cppdefs .= " -DMAXPATCH_PFT=$maxpft";

  if ($bgc_mode eq 'cn') { 
    $cfg_cppdefs .= " -DCN";
  }
  if ($crop eq 'on') { 
    $cfg_cppdefs .= " -DCROP";
  }
  if ($bgc_mode eq 'cndv') { 
    $cfg_cppdefs .= " -DCNDV -DCN";
  }
  if ($nofire eq 'on') {
    $cfg_cppdefs .= " -DNOFIRE";
  }
  if ($noio eq 'on') {
    $cfg_cppdefs .= " -D_NOIO";
  }
  if ($spinup eq 'AD') { 
    $cfg_cppdefs .= " -DAD_SPINUP";
  } elsif ($spinup eq 'exit') { 
    $cfg_cppdefs .= " -DEXIT_SPINUP";
  }
  if ( $snicar_frc eq 'on' ) {
    $cfg_cppdefs .= " -DSNICAR_FRC";
  }
} elsif ($phys->as_long() == $phys->as_long("clm4_5") ) {
  # clm4_5 cppdefs -- SHOULD NOT BE ANY!
  if ( $cfg_cppdefs ne '' ) {
    die <<"EOF";
** CPP definitions should be empty for clm5_0 and is NOT **
EOF
  }
} elsif ($phys->as_long() == $phys->as_long("clm5_0") ) {
  # clm5_0 cppdefs -- SHOULD NOT BE ANY!
  if ( $cfg_cppdefs ne '' ) {
    die <<"EOF";
** CPP definitions should be empty for clm5_0 and is NOT **
EOF
  }
} else {
  # this should NOT happen
  die <<"EOF";
** Bad CLM physics version **
EOF
}
# CPP defines to put on Makefile
my $make_cppdefs = "$usr_cppdefs $cfg_cppdefs";

if ($print>=2) { print "CPP definitions set by configure: \'$cfg_cppdefs\'$eol"; }

#-----------------------------------------------------------------------------------------------
# Write configuration files ####################################################################
#-----------------------------------------------------------------------------------------------

my $fp_filename      = 'Filepath';             # name of output filepath file
my $cpp_filename     = 'CESM_cppdefs';         # name of output file for clm's cppdefs in cesm

# Write the filepath file for cesm.
write_filepath_cesmbld("$clm_bld/$fp_filename", $cfg_ref, $phys, allowEnv=>1 );
if ($print>=2) { print "creating $clm_bld/$fp_filename\n"; }

# Write the file for clm's cppdefs needed in cesm.
write_cppdefs("$clm_bld/$cpp_filename", $make_cppdefs);
if ($print>=2) { print "creating $clm_bld/$cpp_filename\n"; }

# Write the configuration file.
$cfg_ref->write_file($config_cache_file, $commandline);
if ($print>=2) { print "creating $config_cache_file\n"; }

#-----------------------------------------------------------------------------------------------
# Done
chdir( $cwd ) || die <<"EOF";
** Trouble changing directory back to $cwd
**
EOF
if ($print) { print "CLM configure done.\n"; }
exit;

#-----------------------------------------------------------------------------------------------
# FINISHED ####################################################################################
#-----------------------------------------------------------------------------------------------

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

sub write_filepath_cesmbld
{
    my ($file, $cfg_ref, $phys, %opts) = @_;
    my  $fh = new IO::File;

    $fh->open(">$file") or die "** can't open filepath file: $file\n";

    # configuration parameters used to determine paths
    my $usr_src       = $cfg_ref->get('usr_src');
    my $clm_root      = $cfg_ref->get('clm_root');
    my $phys_name     = $phys->as_filename();

    # Source root
    my $srcdir = "$clm_root/src";
    if ( ! &is_valid_directory( "$srcdir", %opts )  ) { die "** source directory does not exist: $srcdir\n"; }

    # User specified source directories.
    if ($usr_src  =~ /\S+/) {
       my @dirs = split ',', $usr_src;
       while ( my $dir = shift @dirs ) {
         print $fh "$dir\n";
       }
    } else {
         print $fh "../SourceMods/src.clm\n";
    }

    my @dirs = ( "cpl", "util_share", "$phys_name/main", "$phys_name/biogeophys", "$phys_name/biogeochem");
    if ($phys->as_long() >= $phys->as_long("clm4_5") ) {
       push (@dirs, "$phys_name/dyn_subgrid");
    }
    foreach my $dir ( @dirs ) {
       if ( &is_valid_directory( "$srcdir/$dir", %opts )  ) {
          print $fh "$srcdir/$dir\n";
       } else {
          die "** source directory does not exist: $srcdir/$dir\n";
       }
    }

    $fh->close;
}
#-------------------------------------------------------------------------------

sub write_cppdefs
{
    my ($file, $make_cppdefs) = @_;
    my  $fh = new IO::File;

    $fh->open(">$file") or die "** can't open cpp defs file: $file\n";

    print $fh "$make_cppdefs\n";
    $fh->close;
}

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


sub absolute_path {
#
# Convert a pathname into an absolute pathname, expanding any . or .. characters.
# Assumes pathnames refer to a local filesystem.
# Assumes the directory separator is "/".
#
  my $path = shift;
  my $cwd = getcwd();  # current working directory
  my $abspath;         # resulting absolute pathname

# Strip off any leading or trailing whitespace.  (This pattern won't match if
# there's embedded whitespace.
  $path =~ s!^\s*(\S*)\s*$!$1!;

# Convert relative to absolute path.

  if ($path =~ m!^\.$!) {          # path is "."
      return $cwd;
  } elsif ($path =~ m!^\./!) {     # path starts with "./"
      $path =~ s!^\.!$cwd!;
  } elsif ($path =~ m!^\.\.$!) {   # path is ".."
      $path = "$cwd/..";
  } elsif ($path =~ m!^\.\./!) {   # path starts with "../"
      $path = "$cwd/$path";
  } elsif ($path =~ m!^[^/]!) {    # path starts with non-slash character
      $path = "$cwd/$path";
  }

  my ($dir, @dirs2);
  my @dirs = split "/", $path, -1;   # The -1 prevents split from stripping trailing nulls
                                     # This enables correct processing of the input "/".

  # Remove any "" that are not leading.
  for (my $i=0; $i<=$#dirs; ++$i) {
      if ($i == 0 or $dirs[$i] ne "") {
	  push @dirs2, $dirs[$i];
      }
  }
  @dirs = ();

  # Remove any "."
  foreach $dir (@dirs2) {
      unless ($dir eq ".") {
	  push @dirs, $dir;
      }
  }
  @dirs2 = ();

  # Remove the "subdir/.." parts.
  foreach $dir (@dirs) {
    if ( $dir !~ /^\.\.$/ ) {
        push @dirs2, $dir;
    } else {
        pop @dirs2;   # remove previous dir when current dir is ..
    }
  }
  if ($#dirs2 == 0 and $dirs2[0] eq "") { return "/"; }
  $abspath = join '/', @dirs2;
  return( $abspath );
}

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

sub subst_env_path {
#
# Substitute for any environment variables contained in a pathname.
# Assumes the directory separator is "/".
#
  my $path = shift;
  my $newpath;         # resulting pathname
  my $nm = "subst_env_path";

# Strip off any leading or trailing whitespace.  (This pattern won't match if
# there's embedded whitespace.
  $path =~ s!^\s*(\S*)\s*$!$1!;

  my ($dir, @dirs2);
  my @dirs = split "/", $path, -1;   # The -1 prevents split from stripping trailing nulls
                                     # This enables correct processing of the input "/".

  foreach $dir (@dirs) {
    if ( $dir =~ m/(^[^\$]*)\$(.*$)/ ) {
        my $startvar = $1;
        my $envvarnm = $2;
        if ( ! defined($ENV{$envvarnm}) ) {
           die "${nm}:: ENV variable $envvarnm is in pathname ($path) -- but NOT defined\n";
        }
        push @dirs2, "$startvar$ENV{$envvarnm}";
    } elsif ( $dir =~ m/\$/) {
        die "${nm}:: malformed ENV variable is in pathname ($path)\n";
    } else {
        push @dirs2, $dir;
    }
  }
  $newpath = join '/', @dirs2;
  return( $newpath );
}

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

sub mkdirp {
    my ($dir) = @_;
    my (@dirs) = split /\//, $dir;
    my (@subdirs, $path);

    # if $dir is absolute pathname then @dirs will start with ""
    if ($dirs[0] eq "") { push @subdirs, shift @dirs; }  

    while ( @dirs ) { # check that each subdir exists and mkdir if it doesn't
	push @subdirs, shift @dirs;
	$path = join '/', @subdirs;
	unless (-d $path or mkdir($path, 0777)) { return 0; }
    }
    return 1;
}

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

sub version {
# The version is found in CLM's ChangeLog file.
# $cfgdir is set by the configure script to the name of its directory.

    my ($cfgdir) = @_;

    my $logfile = "$cfgdir/../doc/ChangeLog";

    my $fh = IO::File->new($logfile, '<') or die "** can't open ChangeLog file: $logfile\n";

    while (my $line = <$fh>) {

	if ($line =~ /^Tag name:\s*[clm0-9_.-]*\s*[toin]*\s*([cesmclm0-9_.-]+)$/ ) {
	    print "$1\n";
	    exit;
	}
    }

}

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

sub is_valid_directory {
#
# Validate that the input is a valid existing directory.
# If allowEnv=>1 expand environment variables.
#
  my ($dir, %opts) = @_;
  my $nm = "is_valid_directory";

  my $valid = 0;
  # Expand environment variables
  if ( $opts{'allowEnv'} ) {
     $dir = subst_env_path( $dir );
  }
  if ( -d $dir ) { $valid = 1; }
  return( $valid );
  
}

