=head1 NAME

CFX5::ShlibExt - Configuring and Building Shared Library-based Extensions

=head1 SYNOPSIS

  use CFX5::ShlibExt;

=head1 DESCRIPTION

This library provides a set of calls for building shared-library based
extensions for ANSYS CFX, currently only the ANSYS CFX Solver.  It can be
broken down into two parts.  The first part contains the configuration
information for various platforms, and the second is the actual
routines for doing the build.

The process is broken down into two parts.  The first part is the
creation of the actual shared object, which is mostly what the user
will reqire.  The second part provides the flags for linking
executables which will use shared libraries, which are mostly for
system use, but may help users to create testbed drivers and so on.

=cut

package CFX5::ShlibExt;

use strict;

use CFX5::Config qw(%Config unsetconf prepend_to_execpath);
use CFX5::Output qw(:all);
use CFX5::Commands qw(parse_rv);
use CFX5::FileTests;
use CFX5::FileOps qw(create_directory);
use CFX5::PathManip qw(join_file basename dirname
                       find_config_file findbin_by_path);

use CFX5::CCLI;

use vars qw($DryRun);

$DryRun='';

=head2 OBJECT INTERFACE

We provide an object-based interface.  Currently, all calls are
class-wide (ie, the object is a singleton), but this could change; use
of this interface protects us from this.

=head3 new

=cut

sub new {
  my $this=shift;
  my $class=ref($this) || $this;
  my $self={};
  bless $self, $class;
  $self;
}

=head2 CONFIGURATION VARIABLES

These settings are the compiler flags etc to use when building a
shared object.  The calls can be made as either class or object calls.

=head3 BUILDING EXTENSIONS

=head4 ext_link

Default command to use, one of ext_link_ld or ext_link_fc.

=cut

sub ext_link {
  my $self=shift;
  my $s=$self->get('ext_link');
  return $s if defined $s;
  return $self->ext_link_use_ld() ? $self->ld() : $self->fc();
}

=head4 ext_link_use_ld

Set to TRUE to link with ld, or FALSE to use the compiler.

=cut

sub ext_link_use_ld {
  my $self=shift;
  my $s=$self->get('ext_link_use_ld');
  return $s if defined $s;
  return '';
}

=head4 ext_link_lib5files

Files from the ANSYS CFX library directory to include in the link stage.

=cut

sub ext_link_lib5files {
  my $self=shift;
  my $s=$self->get_list('ext_link_lib5files');
  return [] unless defined $s;
  return [map { join_file($self->cfx5libdir(),$_); } @$s];
}

=head4 ext_link_flags

Flags to use for linking.  Depends on the setting of ext_link_use_ld.

=cut

sub ext_link_flags {
  my $self=shift;
  my $s=$self->get_list('ext_link_flags');
  return $s if defined $s;
  return $self->ext_link_use_ld() ? $self->ext_link_ldflags() : $self->ext_link_fflags();
}

=head4 ext_link_libs

Libs to use for linking.  Depends on the setting of ext_link_use_ld.

=cut

sub ext_link_libs {
  my $self=shift;
  my $s=$self->get_list('ext_link_libs');
  return $s if defined $s;
  return $self->ext_link_use_ld() ? $self->ext_link_ldlibs() : $self->ext_link_flibs();
}

=head4 ext_link_outopt_exec

Flag to set the output filename.  Depends on the setting of ext_link_use_ld.

=cut

sub ext_link_outopt_exec {
  my $self=shift;
  my $s=$self->get('ext_link_outopt_exec');
  return $s if defined $s;
  return $self->ext_link_use_ld() ? $self->ld_outopt_exec() : $self->fc_outopt_exec();
}

=head4 ext_link_ldflags

Link flags to use if using the linker directly.

=cut

sub ext_link_ldflags {
  my $self=shift;
  my $s=$self->get_list('ext_link_ldflags', 1);
  return $s if defined $s;
  internal_error "Must specify ext_link_ldflags";
}

=head4 ext_link_ldlibs

Link libs to use if using the linker directly.

=cut

sub ext_link_ldlibs {
  my $self=shift;
  my $s=$self->get_list('ext_link_ldlibs');
  return $s if defined $s;
  return [];
}

=head4 ext_link_use_wl

If TRUE, we can make the C and Fortran flags from the ld flags by
prefixing with -Wl.  Otherwise, unless it is set explicitly, they are
assumed to be the same.

=cut

sub ext_link_use_wl {
  my $self=shift;
  my $s=$self->get_list('ext_link_use_wl');
  return $s if defined $s;
  '';
}

=head4 ext_link_cflags

C compiler flags for I<linking>.

=cut

sub ext_link_cflags {
  my $self=shift;
  my $s=$self->get_list('ext_link_cflags');
  return $s if defined $s;
  if ($self->ext_link_use_wl()) {
    my $r=$self->ext_link_ldflags();
    if (ref $r) {
      return [map { "-Wl,$_" } @$r];
    } else {
      return "-Wl,$r";
    }
  } else {
    return $self->ext_link_ldflags();
  }
}

=head4 ext_link_clibs

C compiler libs for I<linking>.

=cut

sub ext_link_clibs {
  my $self=shift;
  my $s=$self->get_list('ext_link_clibs');
  return $s if defined $s;
  if ($self->ext_link_use_wl()) {
    my $r=$self->ext_link_ldlibs();
    if (ref $r) {
      return [map { "-Wl,$_" } @$r];
    } else {
      return "-Wl,$r";
    }
  } else {
    return $self->ext_link_ldlibs();
  }
}

=head4 ext_link_fflags

Fortran compiler flags for linking.

=cut

sub ext_link_fflags {
  my $self=shift;
  my $s=$self->get_list('ext_link_fflags');
  return $s if defined $s;
  return $self->ext_link_cflags();
}

=head4 ext_link_flibs

Fortran compiler libs for linking.

=cut

sub ext_link_flibs {
  my $self=shift;
  my $s=$self->get_list('ext_link_flibs');
  return $s if defined $s;
  return $self->ext_link_clibs();
}

=head4 ext_build_cflags

Flags to pass to the C compiler to compile objects.

=cut

sub ext_build_cflags {
  my $self=shift;
  my $s=$self->get_list('ext_build_cflags', 1);
  return $s if defined $s;
  internal_error("Must specify ext_build_cflags");
}

=head4 ext_build_cxxflags

Flags to pass to the CXX compiler to compile objects.

=cut

sub ext_build_cxxflags {
  my $self=shift;
  my $s=$self->get_list('ext_build_cxxflags');
  return $s if defined $s;
  return $self->ext_build_cflags();
}

=head4 ext_build_fflags

Flags to pass to the Fortran compiler to compile objects.

=cut

sub ext_build_fflags {
  my $self=shift;
  my $s=$self->get_list('ext_build_fflags');
  return $s if defined $s;
  return $self->ext_build_cflags();
}

=head3 ext_subdir_name

=cut

sub ext_subdir_name {
  my $self=shift;
  my $os=$self->os();
  my $attrs=$self->attrs('partial');

  if (defined $attrs and $attrs ne "") {
    return join_file($os,$attrs);
  } else {
    return $os;
  }
}

=head3 BUILDING EXECUTABLES

=head4 exec_link

Default command to use, one of exec_link_ld or exec_link_fc.

=cut

sub exec_link {
  my $self=shift;
  my $s=$self->get('exec_link');
  return $s if defined $s;
  return $self->exec_link_use_ld() ? $self->exec_link_ld() : $self->exec_link_fc();
}

=head4 exec_link_use_ld

Set to TRUE to link with ld, or FALSE to use the compiler.

=cut

sub exec_link_use_ld {
  my $self=shift;
  my $s=$self->get('exec_link_use_ld');
  return $s if defined $s;
  return 1;
}

=head4 exec_link_ld

Default command to use.

=cut

sub exec_link_ld {
  my $self=shift;
  my $s=$self->get('exec_link_ld');
  return $s if defined $s;
  return $self->ld();
}

=head4 exec_link_fc

Default Fortran compiler.

=cut

sub exec_link_fc {
  my $self=shift;
  my $s=$self->get('exec_link_fc');
  return $s if defined $s;
  return $self->fc();
}

=head4 exec_link_cc

Default C compiler.

=cut

sub exec_link_cc {
  my $self=shift;
  my $s=$self->get('exec_link_cc');
  return $s if defined $s;
  return $self->cc();
}

=head4 exec_link_cflags

C compiler flags for I<linking>.

=cut

sub exec_link_cflags {
  my $self=shift;
  my $s=$self->get_list('exec_link_cflags');
  return $s if defined $s;
  return $self->exec_link_ldflags();
}

=head4 exec_link_fflags

Fortran compiler flags for linking.

=cut

sub exec_link_fflags {
  my $self=shift;
  my $s=$self->get_list('exec_link_fflags');
  return $s if defined $s;
  return $self->exec_link_ldflags();
}

=head4 exec_link_ldflags

Link flags to use if using the linker directly.

=cut

sub exec_link_ldflags {
  my $self=shift;
  my $s=$self->get_list('exec_link_ldflags', 1);
  return $s if defined $s;
  internal_error "Must specify exec_link_ldflags";
}

=head4 exec_build_cflags

Flags to pass to the C compiler to compile objects.

=cut

sub exec_build_cflags {
  my $self=shift;
  my $s=$self->get_list('exec_build_cflags', 1);
  return $s if defined $s;
  internal_error("Must specify exec_build_cflags");
}

=head4 exec_build_fflags

Flags to pass to the Fortran compiler to compile objects.

=cut

sub exec_build_fflags {
  my $self=shift;
  my $s=$self->get_list('exec_build_fflags');
  return $s if defined $s;
  return $self->exec_build_cflags();
}

=head3 OTHER COMMON SETTINGS

=head4 ld

The default linker command

=cut

sub ld {
  my $self=shift;
  my $s=$self->get('ld', 1);
  return $s if defined $s;
  internal_error("Must specify ld");
}

=head4 fc

The default Fortran compiler command

=cut

sub fc {
  my $self=shift;
  my $s=$self->get('fc', 1);
  return $s if defined $s;
  internal_error("Must specify fc");
}

=head4 cc

The default C compiler command

=cut

sub cc {
  my $self=shift;
  my $s=$self->get('cc', 1);
  return $s if defined $s;
  internal_error("Must specify cc");
}

=head4 cxx

The default C compiler command

=cut

sub cxx {
  my $self=shift;
  my $s=$self->get('cxx', 1);
  return $s if defined $s;
  internal_error("Must specify cxx");
}

=head4 fc_outopt_obj

Option to pass to the linker to set the name of the output file.

=cut

sub fc_outopt_obj {
  my $self=shift;
  my $s=$self->get('fc_outopt_obj');
  return $s if defined $s;
  ['-o'];
}

=head4 cc_outopt_obj

Option to pass to the linker to set the name of the output file.

=cut

sub cc_outopt_obj {
  my $self=shift;
  my $s=$self->get('cc_outopt_obj');
  return $s if defined $s;
  ['-o'];
}

=head4 cxx_outopt_obj

Option to pass to the linker to set the name of the output file.

=cut

sub cxx_outopt_obj {
  my $self=shift;
  my $s=$self->get('cxx_outopt_obj');
  return $s if defined $s;
  return $self->cc_outopt_obj();
}

=head4 ld_outopt_exec

Option to pass to the linker to set the name of the output file.

=cut

sub ld_outopt_exec {
  my $self=shift;
  my $s=$self->get('ld_outopt_exec');
  return $s if defined $s;
  ['-o'];
}

=head4 fc_outopt_exec

Option to pass to the linker to set the name of the output file.

=cut

sub fc_outopt_exec {
  my $self=shift;
  my $s=$self->get('fc_outopt_exec');
  return $s if defined $s;
  ['-o'];
}

=head4 cc_outopt_exec

Option to pass to the linker to set the name of the output file.

=cut

sub cc_outopt_exec {
  my $self=shift;
  my $s=$self->get('cc_outopt_exec');
  return $s if defined $s;
  ['-o'];
}

=head3 PREPROCESSOR HANDLING

=head4 include_dirs

=cut

sub include_dirs {
  my $self=shift;
  my $s=$self->get_list('include_dirs');
  return $s if defined $s;
  return [$Config{CFX_INCLUDE_DIR}];
}

=head4 cppflags

C preprocessor flags to use for any preprocessed code, C or Fortran.
This does not include the include directories.

=cut

sub cppflags {
  my $self=shift;
  my $s=$self->get_list('cppflags');
  return $s if defined $s;
  # the default ...
  return [];
}

=head4 cppflags_f

Fortran-specific preprocessor flags to use for preprocessed Fortran.
This does not include the include directories.

=cut

sub cppflags_f {
  my $self=shift;
  my $s=$self->get_list('cppflags_f');
  return $s if defined $s;
  # the default ...
  return [];
}

=head4 cppflags_c

C-specific preprocessor flags to use.  This does not include the
include directories.

=cut

sub cppflags_c {
  my $self=shift;
  my $s=$self->get_list('cppflags_c');
  return $s if defined $s;
  # the default ...
  return [];
}

=head4 cppinc_c

=cut

sub _maybe_quote {
  my $dir=shift;
  if ($Config{windows_nt} and $dir =~ m!\s!) {
    return qq("$dir");
  } else {
    return $dir;
  }
}

sub cppinc_c {
  my $self=shift;
  my $s=$self->get_list('cppinc_c');
  return $s if defined $s;
  # the default ...
  my $incflag=$self->cppincflag_c();
  my @incdirs=@{$self->include_dirs()};
  return [map { $incflag . _maybe_quote($_) } @incdirs];
}

=head4 cppinc_f

=cut

sub cppinc_f {
  my $self=shift;
  my $s=$self->get_list('cppinc_f');
  return $s if defined $s;
  # the default ...
  my $incflag=$self->cppincflag_f();
  my @incdirs=@{$self->include_dirs()};
  # on Windows, we need to include double quotes in the command line
  # around arguments containing spaces, to appease the C runtime /
  # compiler.  On UNIX, exec() does what it should, and literal quotes
  # just confuse things ... NB this has to be around just the
  # directory part, not the whole argument, too!
  return [map { $incflag . _maybe_quote($_) } @incdirs];
}

=head4 cppincflag_c

=cut

sub cppincflag_c {
  my $self=shift;
  my $s=$self->get('cppincflag_c');
  return $s if defined $s;
  '-I';
}

=head4 cppincflag_f

=cut

sub cppincflag_f {
  my $self=shift;
  my $s=$self->get('cppincflag_f');
  return $s if defined $s;
  $self->cppincflag_c();
}

=head3 MISC STUFF

=head4 object_extension

The filename extension for an object file.

=cut

sub object_extension {
  my $self=shift;
  my $s=$self->get('object_extension');
  return $s if defined $s;
  'o';
}

=head4 ext_lib_suffix

=cut

sub ext_lib_suffix {
  my $self=shift;
  my $s=$self->get('ext_lib_suffix');
  return $s if defined $s;
  'so';
}

=head4 ext_lib_pattern

=cut

sub ext_lib_pattern {
  my $self=shift;
  my $s=$self->get('ext_lib_pattern');
  return $s if defined $s;
  my $suff=$self->ext_lib_suffix();
  "lib%s.$suff";
}

=head4 compile_only_c

=cut

sub compile_only_c {
  my $self=shift;
  my $s=$self->get('compile_only_c');
  return $s if defined $s;
  '-c';
}

=head4 compile_only_cxx

=cut

sub compile_only_cxx {
  my $self=shift;
  my $s=$self->get('compile_only_cxx');
  return $s if defined $s;
  return $self->compile_only_c();
}

=head4 compile_only_f

=cut

sub compile_only_f {
  my $self=shift;
  my $s=$self->get('compile_only_f');
  return $s if defined $s;
  $self->compile_only_c();
}

=head3 OTHER TRANSIENT SETTINGS

=head4 norun_mode

=cut

sub norun_mode {
  my $self=shift;
  $DryRun;
}

=head3 CONFIGURATION OF THE BUILD

Settings to set, or get, the information to use for this build.  Note
that these flags, unlike the others, are not stored in the CCL
database.

=head4 os

The OS name to use.

=cut

sub os {
  my $self=shift;
  if (@_) {
    $self->{os}=shift;
  }
  $self->{os} || $Config{CFX5_MKEXT_OS} || $Config{CFX5OS};
}

=head4 compiler_obj

The COMPILER object to use.

In 'set' mode, this is determined as the COMPILER object containing
ATTRS objects for all non default attributes. Fatal if not found.

=cut

sub compiler_obj {
  my $self = shift;
  if (@_) {
    my $attrs_mesg = join ' ', $self->attrs('partial');

    my $api=$self->_ccl_api();

    # Find all COMPILER objects inside matching OS object
    my @compiler_objs;
    $api->getObjectsByType("COMPILER", \@compiler_objs);

    for my $compiler_obj (@compiler_objs) {
      my @compiler_obj_path = split '/', $compiler_obj;
      my $compiler = pop @compiler_obj_path;
      my $os = pop @compiler_obj_path;
      for ($os,$compiler) {
        s/.+:\s*//;
        s/\s+/-/g;
      }
      next unless ($os eq $self->os());

      # If all attributes exist as ATTRS objects, set compiler
      my $nattr_found = 0;
      my @attr_list = $self->attrs_validobj('partial');
      for my $attr (@attr_list) {
        my $attr_path = "$compiler_obj/ATTRS:$attr";
        if ($api->haveObject($attr_path)) {
          if ($api->haveParameter("$attr_path/error message")) {
            # got an error in the CCL, abort.
            my $err;
            my $rv = $api->getStringValue("$attr_path/error message",\$err);
            $err = $rv unless $err;
            _abort("Unable to use the requested attribute list \"$attrs_mesg\" on operating system \"".$self->os()."\":", $err);
          } else {
            $nattr_found++;
          }
        }
      }

      $self->{compiler_obj} = $compiler if ($nattr_found == scalar(@attr_list));
    }

    # Check that a matching COMPILER object was found
    unless (defined $self->{compiler_obj}) {
      _abort("No information on building with the requested attribute list \"$attrs_mesg\" for operating system \"".$self->os()."\" is included in the configuration file $Config{CFX5_MKEXT_CMDS}");
    }
  }

  $self->{compiler_obj};
}

=head4 attrs

The I<exact> attribute string to use.

Set mode: Set the attributes. An attributes object must be specified.
          Also determines the COMPILER CCL object.

Get mode: The mode, either 'partial' or 'full' must be specified.
          In list context, return a list of attributes.

=cut

sub attrs {
  my $self = shift;

  if (ref $_[0]) {
    my $attrsobj = shift;
    $self->{attrs_full} = $attrsobj->get_attr_list();
    $self->{attrs_partial} = $attrsobj->partial_attr_list();
    $self->compiler_obj(1);
  } else {
    my $mode = shift;
    my $attrs = $self->{"attrs_$mode"};
    _abort("Error: Invalid mode: $mode in attrs()\n") unless (defined $attrs);

    if (wantarray()) {
      return split '-', $attrs;
    } else {
      return $attrs;
    }    
  }
}

=head4 attrs_validobj

Same as attrs(), but returns alternative names for attributes which
are not valid CCL object names. List context only and return only.

=cut

sub attrs_validobj {
  my $self = shift;
  my $mode = shift;

  # Get functionality only
  _abort("Error: attrs_validobj() cannot be used to set attributes.\n") if (@_);

  # List context only
  _abort("Error: attrs_validobj() cannot return a scalar value.\n") unless (wantarray());
  
  # move 'fort' attributes to the start of the list
  my @attr_list = $self->attrs($mode);
  my @attr_fort = grep(/fort/, @attr_list);
  my @attr_nofort =  grep(!/fort/, @attr_list);
  
  # Prefix invalid object names with 'attr'
  return map { $_ =~ m/^[a-z]/i ? $_ : "attr $_" } (@attr_fort,@attr_nofort);
}

=head4 cfx5libdir

Returns the correct setting of CFX5LIBDIR for this build.

=cut

sub cfx5libdir {
  my $self=shift;
  my $attr_string = $self->attrs('partial');
  return join_file($Config{CFX5ROOT},'lib',
                   $self->os(),($attr_string ? $attr_string : ()));
}

=head3 INTERNAL ACCESS METHODS

=head4 _ccl_api

Returns the internal CCL API instance.  This is different for each
object of this class.  If it has not already been created, it creates
a new instance, and locates and loads the F<cfx5mkext.ccl>
configuration file.

=cut

use CFX5::Config::def CFX5_MKEXT_CMDS => sub {
  # current directory
  if (-f 'cfx5mkext.ccl') {
    return 'cfx5mkext.ccl';
  }

  my $file=find_config_file('cfx5mkext.ccl');
  return $file if defined $file;

  # no user one, assume a system one.
  return join_file($Config{CFX_DATA_DIR},'cfx5mkext.ccl');
};

sub format_ccl_error {
  #my $self=shift;
  (my $str=shift) =~ s!^! !gm;
  substr($str,1);
}
sub _ccl_api {
  my $self=shift;
  if (exists $self->{cclapi}) {
    return $self->{cclapi};
  }

  my $api=CFX5::CCLI::new_api_object();
  # it will abort if it fails to load the library, but if for some
  # reason it then doesn't work we might get undef.
  unless (defined $api) {
    _abort("Unable to access the ANSYS CFX CCL parser.");
  }

  # now load our config file
  my $file=$Config{CFX5_MKEXT_CMDS};
  CFX5::FileTests::datafile($file, "ANSYS CFX Shared Library Builder Commands File $file")
      or exit_if_error();
  local *FILE;
  open FILE, "<$file" or _abort("Error reading configuration file $file: $!");
  my @lines=<FILE>;
  close FILE;
  chomp @lines;

  # and parse it
  my $rv=$api->parseCCLData(\@lines,'');
  if ($rv) {
    _abort("Error parsing configuration file $file:\n",
           format_ccl_error($rv));
  }

  # finally save the API object for Ron
  $self->{cclapi}=$api;
  return $api;
}

=head4 ccl_path

  $self->ccl_path($item, $no_attrs);

Returns the CCL path for a given setting.  If called in a scalar
context, returns the most specific path for the setting.  If called in
a list context, returns a list of all places to look for the setting,
in order.

This order is:

=over

=item *

The "OS" object for this operating system

=item *

The "COMPILER" object

=item *

Inside each such block, the "ATTRS" block for the requested set of
attributes if any, and then the straight contents.

=back

No attempt is made by this function to check existance of the returned
paths.

=cut

sub ccl_path {
  my $self=shift;
  my $item=shift;
  my $noattrs=shift;

  my $os = $self->os();
  my @attrlist = $self->attrs_validobj('full');

  $item =~ s!_! !g;

  my @oslist;
  {
    ( my $osobj = $os ) =~ s!-! !g;
    @oslist="/OS:$osobj";
  }

  my $compiler_obj = sprintf "/COMPILER:%s", $self->compiler_obj();

  if (!wantarray) {
    my $attrs = sprintf "/ATTRS:%s", $attrlist[0];
    return join('',$oslist[0],$compiler_obj,$attrs,"/$item");
  }

  my @rv;
  for my $attr (@attrlist) {
    my $attrs = sprintf "/ATTRS:%s", $attr;
    push @rv, join('',$oslist[0],$compiler_obj,$attrs,"/$item");
  }
  @rv;
}

=head4 get

Returns a value direct from the internal storage, or undef if there is
no such value. For each attribute, the corresponding ATTRS object is
seached. Parameters ocurring in multiple ATTRS objects are concatenated.

=cut

sub get {
  my $self=shift;
  my $item=shift;
  my $essential=shift;

  my @path=$self->ccl_path($item);
  my $api=$self->_ccl_api();

  my @values;
  for my $path (@path) {
    _debug("Trying $path");
    if ($api->haveParameter($path)) {
      my $value;
      $api->getStringValue($path,\$value);
      _debug("$path => ", (defined $value ? $value : '(undef)'));
      push @values, $value;
    }
  }

  # Concatenate multiple instances
  return join (' ',@values) if (scalar(@values));

  # if it's essential, we die here ...
  if ($essential) {
    (my $itemname = $path[0]) =~ s!.*[\\/]!!;
    _abort("Essential (non-synthesisable) configuration parameter $itemname (\"$path[0]\") missing from configuration file $Config{CFX5_MKEXT_CMDS}");
  }
  # ... otherwise, we return "nope"!
  return undef;
}

=head4 get_list

Returns a value from internal storage, in list form rather than as a
scalar, but still returns undef if it is missing.

=cut

sub get_list {
  my $self=shift;
  my $list=$self->get(@_);
  if (defined $list) {
    my $l=[split(' ',$list)];
    _debug('[',@$l,']');
    return $l;
  } else {
    return undef;
  }
}

=head4 set

Sets a value in internal storage.

=cut

sub set {
  my $self=shift;
  my $item=shift;
  my $new=shift;

  my $path=$self->ccl_path($item);

  if (ref($new)) {
    $new=join(' ',@$new);
  }

  my $api=$self->_ccl_api();
  my $rv;

  # HACK Always call setStringValue - needed for ldflags_plus
  # The linker token $ORIGIN is expanded when parsing 
  # but setStringValue will set it correctly afterwards 
  if (!$api->haveParameter($path)) {
    my @levels=split('/',scalar $path);
    shift @levels unless $levels[0]; # remove leading blank
    my $itemname=pop @levels;
    my @ccl;
    for my $l (@levels) {
      push @ccl, $l;
    }
    push @ccl, "$itemname = $new";
    for my $l (@levels) {
      push @ccl, "END # $l";
    }
    $rv=$api->parseCCLData(\@ccl,'');
  }
  $rv=$api->setStringValue($path, $new);

  if ($rv) {
    _abort("Error setting new value for $path:\n",
           format_ccl_error($rv));
  }
  1;
}

=head3 USER SETTINGS

These settings are empty by default, and are designed to be set by the
caller.  This can be done by passing an argument.

=head4 cflags_plus

=cut

sub cflags_plus {
  my $self=shift;
  my $s=$self->get_list('cflags_plus');
  if (@_) {
    $self->set(cflags_plus => [@_]);
  }
  return $s if defined $s;
  [];
}

=head4 cxxflags_plus

=cut

sub cxxflags_plus {
  my $self=shift;
  my $s=$self->get_list('cxxflags_plus');
  if (@_) {
    $self->set(cxxflags_plus => [@_]);
  }
  return $s if defined $s;
  [];
}

=head4 fflags_plus

=cut

sub fflags_plus {
  my $self=shift;
  my $s=$self->get_list('fflags_plus');
  if (@_) {
    $self->set(fflags_plus => [@_]);
  }
  return $s if defined $s;
  [];
}

=head4 ldflags_plus

=cut

sub ldflags_plus {
  my $self=shift;
  my $s=$self->get_list('ldflags_plus');
  if (@_) {
    $self->set(ldflags_plus => [@_]);
  }
  return $s if defined $s;
  [];
}

=head4 ldlibs_plus

=cut

sub ldlibs_plus {
  my $self=shift;
  my $s=$self->get_list('ldlibs_plus');
  if (@_) {
    $self->set(ldlibs_plus => [@_]);
  }
  return $s if defined $s;
  [];
}

=head2 BUILDING

=head3 EXTENSIONS

=head4 ext_compile

  CFX5::ShlibExt->ext_compile('one.f');

=cut

sub ext_compile {
  my $self=shift;
  my $dest=shift;
  my $file=shift;

  my $type=$self->filetype($file);

  unless($type->{source}) {
    return 1;
  }
  if ($type->{c}) {
    return $self->compile_c('ext',$dest,$file);
  } elsif ($type->{fortran}) {
    return $self->compile_fortran('ext',$dest,$file);
  } elsif ($type->{cxx}) {
    return $self->compile_cxx('ext',$dest,$file);
  } else {
    internal_error("Source file $file is neither C nor Fortran?");
  }
}

=head3 SHARED

=head4 compile_c

=cut

sub compile_c {
  my $self=shift;
  my $type=shift;
  my $dest=shift;
  my $file=shift;

  my $build_cflags = "${type}_build_cflags";

  my @flags=ref($self->$build_cflags()) ? @{$self->$build_cflags()} : $self->$build_cflags();
  push @flags, @{$self->cppflags()};
  push @flags, @{$self->cppflags_c()};
  push @flags, @{$self->cppinc_c()};
  push @flags, @{$self->cflags_plus()};

  return $self->do_compile(
                           [
                            $self->cc(),
                            \@flags,
                            $self->cc_outopt_obj(),
                            $self->compile_only_c(),
                           ],
                           $dest,
                           $file,
                          );
}

=head4 compile_cxx

=cut

sub compile_cxx {
  my $self=shift;
  my $type=shift;
  my $dest=shift;
  my $file=shift;

  my $build_cxxflags = "${type}_build_cxxflags";

  my @flags=ref($self->$build_cxxflags()) ? @{$self->$build_cxxflags()} : $self->$build_cxxflags();
  push @flags, @{$self->cppflags()};
  push @flags, @{$self->cppinc_c()};
  push @flags, @{$self->cflags_plus()};
  push @flags, @{$self->cxxflags_plus()};

  return $self->do_compile(
                           [
                            $self->cxx(),
                            \@flags,
                            $self->cxx_outopt_obj(),
                            $self->compile_only_cxx(),
                           ],
                           $dest,
                           $file,
                          );
}

=head4 compile_fortran

=cut

sub compile_fortran {
  my $self=shift;
  my $type=shift;
  my $dest=shift;
  my $file=shift;

  my $build_fflags = "${type}_build_fflags";

  my @flags=ref($self->$build_fflags()) ? @{$self->$build_fflags()} : $self->$build_fflags();
  my $ftyp=$self->filetype($file);
  if ($ftyp->{preprocess}) {
    push @flags, @{$self->cppflags_f()};
    push @flags, @{$self->cppflags()};
    push @flags, @{$self->cppinc_f()};
  }
  push @flags, @{$self->fflags_plus()};

  return $self->do_compile(
                           [$self->fc(),
                            \@flags,
                            $self->fc_outopt_obj(),
                            $self->compile_only_f(),
                           ],
                           $dest,
                           $file,
                          );
}

=head4 link_files

=cut

sub link_files {
  my $self=shift;
  my $type=shift;
  my $dest=shift;

  my $link = "${type}_link";
  my $link_flags = "${type}_link_flags";
  my $link_outopt = "${type}_link_outopt_exec";
  my $link_lib5files = "${type}_link_lib5files";
  my $link_libs = "${type}_link_libs";
  my @flags;
  push @flags, ref($self->$link_flags()) ? @{$self->$link_flags()} : $self->$link_flags();
  push @flags, @{$self->ldflags_plus()};
  my @libs;
  push @libs, @{$self->ldlibs_plus()};
  push @libs, @{$self->$link_lib5files()};
  push @libs, @{$self->$link_libs()};

  return $self->do_compile(
                           [
                            $self->$link(),
                            \@flags,
                            $self->$link_outopt(),
                            undef,
                            \@libs,
                           ],
                           $dest,
                           @_
                          );
}

=head4 do_compile

=cut

sub do_compile {
  my $self=shift;
  my @p=@{shift()};
  my $dest=shift;
  my @src=@_;

  #_print(join(' ',"# Creating $dest from",@src)."\n");

  my @cmd;
  {
    my $c=$self->interpret_compiler($p[0]);
    unless (defined $c) {
      _abort("The compiler command", 
             $self->interpret_compiler_name($p[0]),
             "could not be found.  Please ensure that the compiler is on your PATH and try again.");
    }
    push @cmd, $c;
  }

  # if the options list is an (array) reference, expand it
  if (ref $p[1]) {
    push @cmd, @{$p[1]};
  } else {
    push @cmd, $p[1];
  }

  # if the output option is an (array) reference, it is one or more
  # separate arguments.  Otherwise, it is concatenated with the
  # destination directly.
  if (ref $p[2]) {
    push @cmd, @{$p[2]}, $self->mung_fname($dest);
  } else {
    push @cmd, $p[2].$self->mung_fname($dest);
  }

  push @cmd, $p[3] if defined $p[3];
  push @cmd, map { $self->mung_fname($_) } @src;

  if (defined $p[4]) {
    if (ref $p[4]) {
      push @cmd, @{$p[4]};
    } else {
      push @cmd, $p[4];
    }
  }

  my $rv=$self->do_cmd("Making $dest",@cmd);

  unless ($rv->is_success()) {
    _error($rv->errstr());
    return 0;
  }

  1;
}

=head4 do_cmd

Wrapper around CFX5::Commands::do_cmd, displays the command line,
checks for no-run mode first, and always returns a parsed rv.

=cut

sub do_cmd {
  my $self=shift;
  my $desc=shift;
  my @cmd=@_;

  # re-set the LM_LICENSE_FILE variable if necessary
  local $ENV{LM_LICENSE_FILE};
  if ($Config{LM_LICENSE_FILE_ORIG} ne '(unset)') {
    $ENV{LM_LICENSE_FILE}=$Config{LM_LICENSE_FILE_ORIG};
  } else {
    delete $ENV{'LM_LICENSE_FILE'};
  }

  print join(' ',@cmd), "\n";
  if ($self->norun_mode()) {
    # pretend success
    return parse_rv(0,$desc);
  } else {
    return parse_rv(CFX5::Commands::do_cmd(@cmd),$desc);
  }
}

=head3 INTERNAL

=head4 filetype

Returns a hashref describing the file given as input.  Keys are:

=over

=item source

TRUE if this file needs compiling.

=item object

TRUE if this is already compiled, and needs linking.

=item c

TRUE if this source file is C code.

=item fortran

TRUE if this source file is Fortran code.

=item cxx

TRUE if this source file is C++ code.

=item preprocess

TRUE if this file needs passing through the C preprocessor

=back

=cut

sub filetype {
  my $self=shift;
  my $file=shift;
  my %rv;

  if ($file =~ m!\.(c)$!) {
    $rv{source}='c';
    $rv{c}=1;
  }

  if ($file =~ m!\.(C)$!) {
    if ($Config{windows_nt}) {
      $rv{source}='c';
      $rv{c}=1;
    } else {
      $rv{source}='c++';
      $rv{cxx}=1;
    }
  }

  if ($file =~ m!\.(cc|cxx|c\+\+)$!i) {
    $rv{source}='c++';
    $rv{cxx}=1;
  }

  $rv{preprocess}=1 if $rv{c};

  if ($file =~ m!\.f(77|or|90|tn)?$!i) {
    $rv{source}='f';
    $rv{fortran}=1;
    if ($file =~ m!\.F$!) {
      $rv{preprocess}=1;
    }
  }

  if ($file =~ m!\.o(bj)?$!) {
    $rv{object}='o';
  }
  if ($file =~ m!\.(a|lib)$!) {
    $rv{object}='lib';
  }

  \%rv;
}

=head4 mung_fname

Fixes filenames (replaces / with \ on NT).

=cut

sub mung_fname {
  my $self=shift;
  my $file=shift;
  if ($Config{windows_nt}) {
    $file =~ s!/!\\!g;
  }
  $file;
}

=head4 objectfor

=cut

sub objectfor {
  my $self=shift;
  my $file=shift;

  my $type=$self->filetype($file);
  unless($type->{source}) {
    return $file;
  }

  my $ext=$self->object_extension();
  $file =~ s!\.[^.]+$!.$ext!;
  $file=join_file($self->ext_subdir_name(),basename($file));
  $file;
}

=head4 interpret_compiler

  my $fc=$self->interpret_compiler($self->fc());

Given the compiler name from the configuration file, return the
command to use to actually run the compiler.  If the compiler can be
found, the return value is the command name; otherwise, the return
value is undef.

=cut

sub interpret_compiler {
  my $self=shift;
  my $cmd=shift;
  my $ocmd=$cmd;

  # memoize - to save lots of disc lookups, we stash them away if we
  # calculate them, and re-read them later
  if (exists $self->{compilers}{$cmd}) {
    return $self->{compilers}{$cmd};
  }

  if ($cmd =~ m!\|!) {
    # a set of vertical-bar-separated options, try each at a time
    my @opts=split(/ *\| */,$cmd);
    for my $o (@opts) {
      my $c=$self->interpret_compiler($o);
      if (defined $c) {
        $self->{compilers}{$cmd}=$c;
        return $c;
      }
    }
    # failed
    $self->{compilers}{$cmd}=undef;
    return undef;
  }

  my $pos=findbin_by_path($cmd);
  if ($pos ne '.') {
    $self->{compilers}{$ocmd}=join_file($pos, basename($cmd));
    return $self->{compilers}{$ocmd};
  }

  $self->{compilers}{$ocmd}=undef;
  return undef;
}

=head4 interpret_compiler_name

Returns a textual description of what interpret_compiler would do with
a given compiler string, for use in error messages.

=cut

sub interpret_compiler_name {
  my $self=shift;
  my $cmd=shift;

  if ($cmd =~ m!\|!) {
    # a set of vertical-bar-separated options, try each at a time
    my @opts=split(/ *\| */,$cmd);
    my @rv;
    for my $o (@opts) {
      my $c=$self->interpret_compiler_name($o);
      if (defined $c) {
        push @rv, $c;
      }
    }
    return join(' or ', @rv);
  }

  if ($cmd =~ s;!$;;) {
    #return "\"$cmd\" (with special handling)";
    return "\"$cmd\"";
  }

  return "\"$cmd\"";
}

=head2 OVERALL CONTROL

=head4 BuildLibrary

  CFX5::ShlibExt->BuildLibrary($dest,@sources)
    or exit(1);

=cut

sub BuildLibrary {
  my $self=shift;
  my $dest=shift;
  my @input=@_;
  my $rv;

  my @objects;
  for my $f (@input) {
    my $dest=$self->objectfor($f);
    $self->ext_compile($dest,$f) or return '';
    push @objects, $dest;
  }
  $rv=$self->link_files('ext',$dest,@objects);
  if($rv and $Config{windows_nt}){
    if( -e $dest.'.manifest' ){
      $rv=$self->do_cmd("Embedding manifest", 'mt.exe','/manifest',$dest.'.manifest', '/outputresource:'.$dest.'.;2');
   }
  }
  $rv;
}

=head4 CompileSource

  CFX5::ShlibExt->CompileSource($dest,@sources)
    or exit(1);

=cut

sub CompileSource {
  my $self=shift;
  my $dest=shift;
  my @input=@_;

  my @objects;
  for my $f (@input) {
    my $dest=$self->objectfor($f);
    $self->ext_compile($dest,$f) or return '';
    push @objects, $dest;
  }
  1;
}

=head4 name_from_sources

=cut

sub name_from_sources {
  my $self=shift;
  my $src=shift;
  $src =~ s!.[^.]*$!!g;
  basename($src);
}

=head4 PrepareDirs

  my $file=CFX5::ShlibExt->PrepareDirs($name,$dir,@sources);
  exit 1 unless $file;
  CFX5::ShlibExt->BuildLibrary($file, @sources);

Sets up directories etc for the extension.  Use BuildLibrary to build
it once the directories are prepared.

=cut

sub PrepareDirs {
  my $self=shift;
  my $name=shift;
  my $destdir=shift;
  my @src=@_;

  $name=$self->name_from_sources(@src)
    unless defined $name;

  # first set up the directory
  CFX5::FileTests::datadir_w($destdir,
                             "Destination directory $destdir for extension $name")
      or return '';
  my $subdir=$self->ext_subdir_name();
  my $destsub=join_file($destdir,$subdir);
  unless (-d $destsub) {
    create_directory($destsub)
      or return '';
  } else {
    CFX5::FileTests::datadir_w($destdir,
                               "Destination directory $destsub for extension $name")
        or return '';
  }

  # we also need the subdirectory in the current directory, for the
  # objects etc
  unless (-d $subdir) {
    create_directory($subdir)
      or return '';
  } else {
    CFX5::FileTests::datadir_w($subdir,
                               "Intermediate directory $subdir for extension $name")
        or return '';
  }

  my $filename=sprintf($self->ext_lib_pattern(),$name);
  my $destfile=join_file($destsub,$filename);
  CFX5::FileTests::datafile_w($destfile,
                              "Extension library $destfile")
      or return '';


  return $destfile;
}

1;
