#!/usr/bin/env perl # # (C) 2011by Argonne National Laboratory. # See COPYRIGHT in top-level directory. # # script TODO: # - generate initialization function to read vals from env vars # - deal with string escaping in generated C strings # - sort/collate paramters by name/category use strict; use warnings; # help perl find the YAML parsing module use lib 'maint/local_perl/lib'; use YAML::Tiny qw(); use File::Basename qw(basename); use Data::Dumper; use Getopt::Long; # I'm pretty sure this is a standard lib module across all perl5 # installs, but we can work around this easily if that doesn't turn out # to be true. [goodell@ 2010-04-26] use Digest::MD5 qw(); # To format README file use Text::Wrap; $Text::Wrap::unexpand = 0; # disable hard tabs in output ################################################## # set true to enable debug output my $debug = 0; # namespace prefix for variable and type names my $ns = "MPIR_Param"; # an alternative namespace used for environment variables, unused if set # to "" my $alt_ns = "MPICH"; # parameter description file my $param_file = "src/util/param/params.yml"; # output source files my $header_file = "src/include/mpich_param_vals.h"; my $c_file = "src/util/param/param_vals.c"; my $readme_file = "README.envvar"; GetOptions( "help!" => \&print_usage_and_exit, "debug!" => \$debug, "namespace=s" => \$ns, "alt-namespace=s" => \$alt_ns, "param-file" => \$param_file, "header=s" => \$header_file, "c-file=s" => \$c_file, "readme-file=s" => \$readme_file, ) or die "unable to parse options, stopped"; sub print_usage_and_exit { print <new(); my $params = ($yaml->read($param_file))->[0]; # [0] is for the first document print Dumper($params)."\n" if $debug; die "not a HASH, stopped" unless ref($params) eq "HASH"; ######################################################################## # validate the config file # only simple checks for now, just make sure that all categories # referenced by parameters actually exist my %cat_hash = (map { ($_->{name} => 1) } @{$params->{categories}}); foreach my $p (@{$params->{parameters}}) { unless (exists $cat_hash{$p->{category}}) { warn "category '".$p->{category}."' referenced by '".$p->{name}."' was not found"; } } ######################################################################## # setup output files open(PARAM_HDR, '>', $header_file); open(PARAM_C, '>', $c_file); open(PARAM_README, '>', $readme_file); my $hdr_guard = header_to_incl_guard($header_file); my $param_file_md5 = md5sum($param_file); print PARAM_HDR <{categories}; # write enum first print PARAM_HDR <{categories}}; foreach my $cat (@{$params->{categories}}) { printf PARAM_HDR " ${uc_ns}_CATEGORY_ID_%s,\n", $cat->{name}; } # then write full contents tuple print PARAM_HDR <{categories}}) { my $desc = $cat->{description}; $desc =~ s/"/\\"/g; printf PARAM_C qq( { ) . qq(${uc_ns}_CATEGORY_ID_%s,\n) . qq( "%s",\n) . qq( "%s" },\n), $cat->{name}, $cat->{name}, $desc; } print PARAM_C <{parameters}; print PARAM_HDR <{parameters}}; # XXX DJG TODO collate and separate by category foreach my $p (@{$params->{parameters}}) { printf PARAM_HDR " ${uc_ns}_ID_%s,\n", $p->{name}; } print PARAM_HDR <{parameters}}) { printf PARAM_HDR "extern %s ${uc_ns}_%s;\n", type2ctype($p->{type}), $p->{name}; } print PARAM_C <{parameters}}) { my $type_enum_val = "${uc_ns}_TYPE_".uc($p->{type}); my ($int_val, $str_val, $double_val, $range_val) = (-1, qq(""), "0.0", "{0,0}"); if ($p->{type} eq "string") { $str_val = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } elsif ($p->{type} eq "int") { $int_val = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } elsif ($p->{type} eq "double") { $double_val = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } elsif ($p->{type} eq "boolean") { $int_val = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } elsif ($p->{type} eq "range") { $range_val = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } else { die "unknown type $p->{type}, stopped"; } my $desc = $p->{description}; $desc =~ s/"/\\"/g; printf PARAM_C qq( { ) . qq(${uc_ns}_ID_%s,\n) . qq( "%s",\n) . qq( "%s",\n) . # T I D S R ptr qq( { %s, %s, %s, %s, %s }, %s },\n), $p->{name}, $p->{name}, $desc, $type_enum_val, $int_val, $double_val, $str_val, $range_val, "&${uc_ns}_".$p->{name}; } print PARAM_C <{parameters}}) { my $default; if ($p->{type} eq "string") { # handle strings specially to avoid various const issues $default = fmt_default($p->{name}, undef, "NULL", $p->{type}); } else { $default = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); } printf PARAM_C "%s ${uc_ns}_%s = %s;\n", type2ctype($p->{type}), $p->{name}, $default; } # FIXME the mpi_errno bit is MPICH-specific print PARAM_C <{parameters}}) { my $env_fn = type_to_env_fn($p->{type}); my @env_names = (); my $var_name = "${uc_ns}_" . $p->{name}; my $var_suffix = $p->{name}; # process extra envs first so the primary always wins push @env_names, @{$p->{'abs-alt-env'}} if $p->{'abs-alt-env'}; push @env_names, map { "${alt_ns}_$_" } @{$p->{'alt-env'}}; push @env_names, map { "${uc_ns}_$_" } @{$p->{'alt-env'}}; push @env_names, "${alt_ns}_" . $p->{name}; push @env_names, "${uc_ns}_" . $p->{name}; if ($p->{type} eq 'string') { print PARAM_C <{type} eq 'range') { print PARAM_C <{type} eq 'string') { print PARAM_C <{type} eq 'string') { print PARAM_C <{parameters}}) { my $var_name = "${uc_ns}_" . $p->{name}; if ($p->{type} eq "string") { # need to cleanup after whatever was strduped by the init routine print PARAM_C <{parameters}}) { my @env_names = (); my $first; my $alt; my $default; # process extra envs first so the primary always wins push @env_names, map { "${alt_ns}_$_" } @{$p->{'alt-env'}}; push @env_names, "${uc_ns}_" . $p->{name}; push @env_names, map { "${uc_ns}_$_" } @{$p->{'alt-env'}}; push @env_names, @{$p->{'abs-alt-env'}} if $p->{'abs-alt-env'}; print PARAM_README "${alt_ns}_$p->{name}\n"; $first = 1; foreach $alt (@env_names) { if ($first) { print PARAM_README " Aliases: $alt\n"; } else { print PARAM_README " $alt\n"; } $first = 0; } print PARAM_README wrap(" Description: ", " ", $p->{description} . "\n"); $default = fmt_default($p->{name}, $p->{default}, $p->{defaultliteral}, $p->{type}); print PARAM_README " Default: $default\n"; print PARAM_README "\n"; } ######################################################################## # clean up close(PARAM_C); print PARAM_HDR < 'int', 'double' => 'double', 'string' => 'char *', 'boolean' => 'int', 'range' => "${ns}_param_range_val_t", ); die "unknown type '$type', stopped" unless exists $typemap{$type}; return $typemap{$type}; } # transform a default value into a C value sub fmt_default { my $name = shift; my $val = shift; my $literalval = shift; my $type = shift; if (defined($literalval)) { die "Both \"default\" and \"defaultliteral\" fields were specified for parameter \"$name\", stopped" if defined($val); return qq($literalval); } die "Exactly one of \"default\" or \"defaultliteral\" fields must be specified for parameter \"$name\", stopped" unless defined($val); if ($type eq "string") { $val =~ s/"/\\"/g; return qq("$val"); } elsif ($type eq "boolean") { if ($val =~ m/^(0|f(alse)?|no?)$/i) { return qq(0); } elsif ($val =~ m/^(1|t(rue)?|y(es)?)$/i) { return qq(1); } else { warn "WARNING: type='$type', bad val='$val', continuing"; return qq(0); # fail-false } } elsif ($type eq "range") { if ($val !~ "-?[0-9]+:-?[0-9]+") { die "Unable to parse range value '$val', stopped"; } $val =~ s/:/,/; return qq({$val}); } else { return qq($val); } } # turns foo_BAR-baz.h into FOO_BAR_BAZ_H_INCLUDED sub header_to_incl_guard { my $header_file = shift; my $guard = basename($header_file); $guard =~ tr/a-z\-./A-Z__/; $guard .= "_INCLUDED"; die "guard contains whitespace, stopped" if ($guard =~ m/\s/); return $guard; } sub md5sum { my $file = shift; my $md5 = Digest::MD5->new(); open FILE, '<', $file; binmode(FILE); $md5->addfile(*FILE); close FILE; return $md5->hexdigest; } sub type_to_env_fn { my $type = shift; my %typemap = ( 'int' => 'int', 'string' => 'str', 'boolean' => 'bool', 'double' => 'double', 'range' => 'range', ); die "unknown type '$type', stopped" unless exists $typemap{$type}; return $typemap{$type}; }