#!/usr/bin/env perl #----------------------------------------------------------------------------------------------- # # configure # # This utility allows the CCSM utilities user to specify configuration # options via a commandline interface. # #----------------------------------------------------------------------------------------------- use strict; #use warnings; #use diagnostics; use Cwd; use English; use Getopt::Long; use IO::File; use IO::Handle; #----------------------------------------------------------------------------------------------- # 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 CCSM configuration scripts. If the create_newcase 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 $ccsmroot = absolute_path($ENV{CCSMROOT}); (-d "$ccsmroot") or die <<"EOF"; ** Cannot find ccsmroot directory \"$ccsmroot\" Set using environment variable CCSMROOT ** EOF my $cfgdir = "$ccsmroot/scripts"; #----------------------------------------------------------------------------------------------- if ($#ARGV == -1) { usage(); } my $machdir="$cfgdir/ccsm_utils/Machines"; #----------------------------------------------------------------------------------------------- sub usage { die <). 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. -mach Specify a CCSM machine (required). -compiler Specify a compiler for the target machine (optional) default: default compiler for the target machine -mpilib Specify a mpi librar for the target machine (optional) default: default mpi library for the target machine -mach_dir Specify the locations of the Machines directory (optional). default: $machdir -output_dir default: current working directory -help [or -h] Print usage to STDOUT (optional). -list Only list valid values for machines and compilers (optional). -silent [or -s] Turns on silent mode - only fatal messages issued (optional). -verbose [or -v] Turn on verbose echoing of settings made by create_newcase (optional). The following arguments are required for a generic machine. Otherwise, they will be ignored. -scratchroot cesm executable directory (EXEROOT will be scratchroot/CASE) (char) -din_loc_root cesm input data root directory (char) -max_tasks_per_node maximum mpi tasks per machine node (integer) EXAMPLES ./configure -mach bluefire -compiler ibm ./configure -mach generic_CNL -compiler cray EOF } #----------------------------------------------------------------------------------------------- # Save commandline my $commandline = "configure @ARGV"; #----------------------------------------------------------------------------------------------- # Parse command-line options. my %opts = ( mach_dir => $machdir, max_tasks_per_node => 1, ); GetOptions( "compiler=s" => \$opts{'compiler'}, "mpilib=s" => \$opts{'mpilib'}, "h|help" => \$opts{'help'}, "list" => \$opts{'list'}, "mach=s" => \$opts{'mach'}, "mach_dir=s" => \$opts{'mach_dir'}, "output_dir=s" => \$opts{'output_dir'}, "s|silent" => \$opts{'silent'}, "v|verbose" => \$opts{'verbose'}, "scratchroot=s" => \$opts{'scratchroot'}, "din_loc_root=s" => \$opts{'din_loc_root'}, "max_tasks_per_node=i" => \$opts{'max_tasks_per_node'}, ) or usage(); # Give usage message. usage() if $opts{'help'}; # Check for unparsed argumentss if (@ARGV) { print "ERROR: unrecognized arguments: @ARGV\n"; usage(); } # Check for manditory case input if not just listing valid values my $mach; my $compiler; my $mpilib; if (!$opts{'list'}) { # Check for manditory machine input if ($opts{'mach'}) { $mach = $opts{'mach'}; if ($mach =~ "generic") { if (!$opts{'scratchroot'}) { die "ERROR: configure must include the argument -scratchroot for a generic machines \n"; } if (!$opts{'din_loc_root'}) { die "ERROR: configure must include the argument -din_loc_root for a generic machines \n"; } if (!$opts{'max_tasks_per_node'}) { die "ERROR: configure must include the argument -max_tasks_per_node for a generic machines \n"; } my $din_loc_root = $opts{'din_loc_root'}; (-d "$din_loc_root") or die "Cannot find din_loc_root directory $din_loc_root"; } } else { die "ERROR: configure must include the input argument, -mach \n"; } # Check if machine compiler option is given if ($opts{'compiler'}) { $compiler = $opts{'compiler'}; } # Check if machine mpilib option is given if ($opts{'mpilib'}) { $mpilib = $opts{'mpilib'}; } } # Check for the configuration definition file. my $config_def_file = "config_definition.xml"; (-f "$cfgdir/ccsm_utils/Case.template/$config_def_file") or die <<"EOF"; ** Cannot find configuration definition file \"$config_def_file\" in directory \"$cfgdir/ccsm_utils/Case.template/$config_def_file\" ** EOF # Set machdir to default or value sent in on command line $machdir=$opts{'mach_dir'}; my $output_dir = "."; $output_dir = $opts{output_dir} if defined( $opts{output_dir} ); # # Make sure the output_dir exists or can be created # if(! -d "$output_dir") { mkdir $output_dir, "755" or die "Could not find or create $output_dir"; } # 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 the Machines directory # (-d "$machdir") or die <<"EOF"; ** Cannot find cesm Machines directory \"$machdir\" ** EOF #----------------------------------------------------------------------------------------------- # Make sure we can find required perl modules and configuration files. # Look for them in the directory that contains the configure script. # Machines definition file. my $machine_file = 'config_machines.xml'; (-f "$machdir/$machine_file") or die <<"EOF"; ** Cannot find machine parameters file \"$machine_file\" in directory \"$machdir\" ** EOF # Compiler definition file. my $compiler_file = 'config_compilers.xml'; (-f "$machdir/$compiler_file") or die <<"EOF"; ** Cannot find compiler parameters file \"$compiler_file\" in directory \"$machdir\" ** EOF # The XML::Lite module is required to parse the XML configuration files. (-f "$cfgdir/ccsm_utils/Tools/perl5lib/XML/Lite.pm") or die <<"EOF"; ** Cannot find perl module \"XML/Lite.pm\" in directory \"$cfgdir/ccsm_utils/Tools/perl5lib\" ** EOF if ($print>=2) { print "Setting configuration directory to $cfgdir$eol"; } #----------------------------------------------------------------------------------------------- # Add $cfgdir/perl5lib to the list of paths that Perl searches for modules my @dirs = ( $cfgdir, "$cfgdir/ccsm_utils/Case.template", "$cfgdir/ccsm_utils/Tools/perl5lib", "$cfgdir/ccsm_utils/Tools"); unshift @INC, @dirs; require XML::Lite; require ConfigCase; require SetupTools; #----------------------------------------------------------------------------------------------- # If just listing valid values then exit after completion of lists if ($opts{'list'}) { ConfigCase::print_machines("$machdir/$machine_file"); # to do - add print_compilers if ($print>=2) { print "finished listing valid values, now exiting $eol"; } exit; } #----------------------------------------------------------------------------------------------- # Create new config object if not just listing valid values my $cfg_ref = ConfigCase->new("$cfgdir/ccsm_utils/Case.template/$config_def_file"); #if ($print>=2) { print "A new config reference object was created$eol";} #----------------------------------------------------------------------------------------------- # (4) Machine parameters if($mach =~ /generic_(.*)/){ $cfg_ref->set('OS',$1); $cfg_ref->set('MACH','generic'); }else{ $cfg_ref->set_machine("$machdir/$machine_file", $mach, $print); } $cfg_ref->set('CCSM_MACHDIR', "$machdir"); if ($mach =~ "generic") { my $scratchroot = "$opts{'scratchroot'}"; $cfg_ref->set('DIN_LOC_ROOT' , $opts{'din_loc_root'}); $cfg_ref->set('MAX_TASKS_PER_NODE' , $opts{'max_tasks_per_node'}); $cfg_ref->set('GMAKE_J' , "1"); } # Check that compiler request for target machine matches a supported value # Or set default compiler - if not provided compiler request my $compilers = $cfg_ref->get('COMPILERS'); my @compilers = split ",", $compilers, -1; if ($compiler) { if (! ($mach =~ "generic")){ my $found = 0; foreach my $comp (@compilers) { if ($compiler eq $comp) { $found = 1; } } if (!$found) { my $sysmod = "rm -rf $output_dir"; system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n"; die "ERROR: compiler setting of $compiler does not match supported values of $compilers \n"; } } $cfg_ref->set('COMPILER', "$compiler"); if ($print>=2) { print "Machine compiler specifier: $compiler.$eol"; } } else { $compiler = $compilers[0]; $cfg_ref->set('COMPILER', "$compiler"); if ($print>=2) { print "Machine compiler specifier: $compiler.$eol"; } } my $mpilibs = $cfg_ref->get('MPILIBS'); my @mpilibs = split ",", $mpilibs, -1; if ($mpilib) { # check that mpilib request for target machine matches a supported value my $found = 0; foreach my $mpi (@mpilibs) { if ($mpilib eq $mpi) { $found = 1; } } if (!$found) { my $sysmod = "rm -rf $output_dir"; system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n"; die "ERROR: mpilib setting of $mpilib does not match supported values of $mpilibs \n"; } $cfg_ref->set('MPILIB', "$mpilib"); if ($print>=2) { print "Machine mpilib specifier: $mpilib.$eol"; } } else { $mpilib = $mpilibs[0]; $cfg_ref->set('MPILIB', "$mpilib"); if ($print>=2) { print "Machine mpilib specifier: $mpilib.$eol"; } } if ($print>=2) { print "Machine specifier: $mach.$eol"; } my $repotag; if (-f "$ccsmroot/ChangeLog") { $repotag =`cat $ccsmroot/ChangeLog | grep 'Tag name:' | head -1`; } else { $repotag =`cat $ccsmroot/models/atm/cam/doc/ChangeLog | grep 'Tag name:' | head -1`; } my @repotag = split(/ /,$repotag); $repotag = $repotag[2]; chomp($repotag); $cfg_ref->set('CCSM_REPOTAG', $repotag); SetupTools::set_compiler($cfg_ref->get('OS'),"$machdir/$compiler_file",$compiler, $mach, $print, "$output_dir/Macros"); my $sysmod; # Create machine specific environment file (env_mach_specific) if(-e "$machdir/env_mach_specific.$mach" ) { $sysmod = "cp $machdir/env_mach_specific.$mach $output_dir/env_mach_specific"; }else{ $sysmod = "touch $output_dir/env_mach_specific"; } system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n"; # Copy Depends files if they exist if( -e "$machdir/Depends.$mach" ) { $sysmod = "cp $machdir/Depends.$mach $output_dir/"; system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n"; } if( -e "$machdir/Depends.$compiler" ) { $sysmod = "cp $machdir/Depends.$compiler $output_dir/"; system($sysmod) == 0 or die "ERROR: $sysmod failed: $?\n"; } # Finally include a csh wrapper to load the modules and run the make open(F,">build.csh") or die "Could not open file build.csh"; print F "#!/usr/bin/env csh\n"; print F "#\n# Use this script to assure that modules are properly loaded before compiling\n#\n"; print F "setenv MPILIB ".$cfg_ref->get('MPILIB')."\n"; print F "setenv COMPILER $compiler\n"; print F "source env_mach_specific\n"; print F "gmake \$*\n"; close(F); chmod(0755,"build.csh"); print "Successfully created auxilary build files for $mach \n\n"; exit; #----------------------------------------------------------------------------------------------- # FINNISHED #################################################################################### #----------------------------------------------------------------------------------------------- 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 # 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 =~ /^\$(.+)$/ ) { push @dirs2, $ENV{$1}; } else { push @dirs2, $dir; } } $newpath = join '/', @dirs2; return( $newpath ); } #------------------------------------------------------------------------------- #-------------------------------------------------------------------------------