/usr/local/CPAN/Inline-SLang/Makefile.PL


#
# Setup Inline::SLang
# - code taken from Makefile.PL in Inline-Ruby-0.02
#   (Neil Watkiss), although all errors should be assumed to
#   be mine and not Neil's
#

#
# This software is Copyright (C) 2003, 2004, 2005 Smithsonian
# Astrophysical Observatory. All rights are reserved.
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# 02111-1307 USA
# 
# Or, surf on over to
# 
#  http://www.fsf.org/copyleft/gpl.html
#

#
# Notes:
#  - we use Inline::C to interface to the S-Lang library (prior to
#    0.22 we had the code in an external file which we compiled
#    ourselves). Hence we have to include a check for Inline/Inline::C
#    at the start of the script.
#
#  - Have tried to use File::Spec to ensure works across different
#    filesystems, however:
#    a) I don't know the rules for other systems so I can't promise
#       that I've done things correctly (and they're almost certain
#       not sensible as I'm reluctant to cash many results)
#    b) Other parts of the code still contain UNIX-isms
#

eval { "require 5.6.0" } or die <<EOD;
############
###
### The Inline::SLang module requires at least Perl version v5.6.0.
### Please upgrade your Perl before installing this module.
###
############
EOD

eval { "require Inline::C;" } or die <<EOD;
############
###
### The Inline::SLang module requires that Inline::C be installed.
### It is needed to build the module. You only need the Inline module
### to use Inline::SLang.
###
############
EOD

use strict;

use Config;
##use Data::Dumper;
use ExtUtils::MakeMaker;
use Getopt::Long;
use IO::File;
use File::Spec; # initial try to allow compilatio on non Unix-like filesystems

## Internal routines

# we start with the fixed-size types and then
# the 'standard' types
#
my @intypes = qw(
  Int16_Type UInt16_Type
  Int32_Type UInt32_Type
  Int64_Type UInt64_Type
  Float32_Type Float64_Type 
  Short_Type UShort_Type
  Long_Type ULong_Type
  Int_Type UInt_Type
);

my %synonyms;
my %sl_sizes;

# notes:
#  - fills in %synonyms and %sl_sizes arrays
#  - creates stf.h
#
sub parse_slconfig () {

    foreach my $in ( @intypes ) {
	my $out = sl_type_conversion( $in );
	next unless defined $out and $in ne $out;
	$synonyms{$in} = $out;
    }

    # Convert this to code used by _sl_defined_types in SLang.xs
    # - what a lovely mess I'm making
    #   (stf = slang-type-fragment but trying to be sensitive to
    #          filename-challenged filesystems even though other parts
    #          of this script are UNIX-specific)
    #
    my $cfrag = "stf.h";
    unlink $cfrag if -e $cfrag;
    die "Error: Unable to remove $cfrag\n" if -e $cfrag;

    my $fh = IO::File->new( "> $cfrag" )
      or die "Error: Unable to create $cfrag\n";
    while ( my ( $synonym, $base ) = each %synonyms ) {
	unless ( exists $sl_sizes{$base} ) {
	    # $synonym looks like xxx\d\d_Type so we want to extract the
            # numbers only
            #
	    if ( $synonym =~ m/^\D+(\d+)_Type$/ ) {
		$sl_sizes{$base} = $1 / 8;
	    }
	}

	my $len = length($synonym);
	$fh->print( <<"EOT");
  arrayref = newAV(); av_extend( arrayref, (I32) 2 );
  av_store( arrayref, 0, newSVpv("$base",0) );
  av_store( arrayref, 1, newSViv( 2 ) );
  (void) hv_store( hashref, "$synonym", $len, newRV_inc( (SV *) arrayref ), 0 );
EOT
    }
    $fh->close;

    # why do I have this?
    #
    if ( $synonyms{Long_Type} eq "Integer_Type" ) {
	$sl_sizes{Long_Type} = $sl_sizes{Integer_Type};
    }
} # sub: parse_slconfig

#
# Usage:
#   $include_flags = add_pdl_support();
#
# Aim:
#   Sets up things to alow Inline::SLang to be compiled with
#   support for PDL.
#
#   The return value is "" if PDL support is not available,
#   otherwise it is the include flags needed to add to the make file
#   to get C code to compile.
#
# Notes:
#   creates topdl.h and toslang.h - they need adding to
#   the clean target of the make file?
#
#   %sl_sizes must have been created before this routine is called,
#   which means parse_slconfig musy have been called
#
sub add_pdl_support () {

  # should I try and enforce a version of PDL?
  eval "use PDL;";

  # since --pdl is on by default I only make it a warning when
  # it cannot be found
  #
  if ( $@ ) {
    print "\nWARNING: PDL support has been disabled as not found on system\n\n";
    return "";
  }

  my $tmp = 1;
  my $pdlver = 0;
  map {
    $pdlver += ($_ * $tmp);
    $tmp *= 100;
  } reverse split( /\./, $PDL::VERSION );
  if ( $pdlver < 20400 ) {
    print <<"EOW";

WARNING:
  Found PDL but its version ($PDL::VERSION) is < 2.4.0 so things
  may not work, such as this build...

EOW
  }

  require PDL::Core::Dev;

  # work out the mapping from S-Lang datatype to PDL datatype
  #
  # S-Lang numeric types are:
  #   [U]Char_Type
  #   [U]Short_Type
  #   [U]Integer_Type
  #   [U]Long_type
  #   Float_Type
  #   Double_Type
  #
  # PDL types are:
  #   PDL_Byte -- this is unsigned
  #   PDL_Short
  #   PDL_UShort
  #   PDL_Long
  #   PDL_LongLong [maybe]
  #   PDL_Float
  #   PDL_Double
  #
  # So no unsigned char/long, so convert as signed type.
  # Assume that byte <-> char and have a size of 1.
  #
  # the following bit of code delves into the internals somewhat
  # - so is a bit dangerous - and requires a recent PDL (2.4.0)
  #
  require PDL::Types;
  my %pdl_sizes;
  foreach my $ptype ( PDL::Types::typesrtkeys() ) {
    # going to assume unsigned types have the same size as signed types
    my $tmp = PDL::Types::typefld( $ptype, "realctype" );
    $tmp =~ s/^unsigned //;
    $tmp =~ s/\s+//g;
    $tmp .= "size";
    # as a ***HACK*** I am going to assume that a float is 4 bytes wide
    # - this needs re-working
    if ( $tmp eq "floatsize" ) {
      $pdl_sizes{$ptype} = 4;
    } else {
      $pdl_sizes{$ptype} = $Config{$tmp} || die "Error: unable to find size of '$tmp' from Config.pm\n";
    }
  }

  ##use Data::Dumper; print Dumper( \%sl_sizes ), "\n";

  # find out how the S-Lang types map onto PDL ones
  # - can you guess this was done without any thinking?
  #
  my %sl_typemaps;
  my %pl_typemaps;
  if ( $sl_sizes{Short_Type} == $pdl_sizes{PDL_S} ) {
    $sl_typemaps{SHORT} = [ "PDL_S", $pdl_sizes{PDL_S} ];
    $pl_typemaps{PDL_S} = [ "SHORT", $pdl_sizes{PDL_S} ];
  } else {
    print "Errr: need to sort out Short_Type\n";
  }
  if ( $sl_sizes{UShort_Type} == $pdl_sizes{PDL_US} ) {
    $sl_typemaps{USHORT} = [ "PDL_US", $pdl_sizes{PDL_US} ];
    $pl_typemaps{PDL_US} = [ "USHORT", $pdl_sizes{PDL_US} ];
  } else {
    print "Errr: need to sort out UShort_Type\n";
  }
  if ( $sl_sizes{Integer_Type} == $pdl_sizes{PDL_L} ) {
    $sl_typemaps{INT}   = [ "PDL_L", $pdl_sizes{PDL_L} ];
    $sl_typemaps{UINT}  = [ "PDL_L", $pdl_sizes{PDL_L} ];
    $pl_typemaps{PDL_L} = [ "INT", $pdl_sizes{PDL_L} ];
  } else {
    print "Errr: need to sort out Integer_Type\n";
  }
  if ( $sl_sizes{Long_Type} == $pdl_sizes{PDL_L} ) {
    $sl_typemaps{LONG}  = [ "PDL_L", $pdl_sizes{PDL_L} ];
    $sl_typemaps{ULONG} = [ "PDL_L", $pdl_sizes{PDL_L} ];
  } else {
    print "Errr: need to sort out Long_Type\n";
  }
  if ( $sl_sizes{Float_Type} == $pdl_sizes{PDL_F} ) {
    $sl_typemaps{FLOAT} = [ "PDL_F", $pdl_sizes{PDL_F} ];
    $pl_typemaps{PDL_F} = [ "FLOAT", $pdl_sizes{PDL_F} ];
  } else {
    print "Errr: need to sort out Float_Type\n";
  }
  if ( $sl_sizes{Double_Type} == $pdl_sizes{PDL_D} ) {
    $sl_typemaps{DOUBLE} = [ "PDL_D", $pdl_sizes{PDL_D} ];
    $pl_typemaps{PDL_D}  = [ "DOUBLE", $pdl_sizes{PDL_D} ];
  } else {
    print "Errr: need to sort out Double_Type\n";
  }

  # Convert this to code used by sl2pl_array_pdl in sl2pl.c
  # - what a lovely mess I'm making
  #
  my $cfrag = "topdl.h";
  unlink $cfrag if -e $cfrag;
  die "Error: Unable to remove $cfrag\n" if -e $cfrag;

  my $fh = IO::File->new( "> $cfrag" )
    or die "Error: Unable to create $cfrag\n";
  $fh->print(
	     "if ( SLANG_CHAR_TYPE == at->data_type || SLANG_UCHAR_TYPE == at->data_type )\n" .
	     "  { out->datatype = PDL_B; dsize = 1; }\n"
	     );
  while ( my ( $stype, $aref ) = each %sl_typemaps ) {
    $fh->print(
	       "else if ( SLANG_${stype}_TYPE == at->data_type )\n" .
	       "  { out->datatype = $$aref[0]; dsize = $$aref[1]; }\n"
	       );
  }
  $fh->print( "else { croak(\"ERROR: Unable to convert an array of %s to a piddle\",\n" .
	      "SLclass_get_datatype_name(at->data_type)); }\n" );
  $fh->close;

  # ditto for pl2sl_type() in pl2sl.c
  $cfrag = "toslang.h";
  unlink $cfrag if -e $cfrag;
  die "Error: Unable to remove $cfrag\n" if -e $cfrag;

  $fh = IO::File->new( "> $cfrag" )
    or die "Error: Unable to create $cfrag\n";
  $fh->print(
	     "if ( PDL_B == pdl->datatype )\n" .
	     "  { otype = SLANG_UCHAR_TYPE; dsize = 1; }\n"
	     );
  while ( my ( $stype, $aref ) = each %pl_typemaps ) {
    $fh->print(
	       "else if ( $stype == pdl->datatype )\n" .
	       "  { otype = SLANG_" . $$aref[0] . "_TYPE; dsize = $$aref[1]; }\n"
	       );
  }
  $fh->print( "else { croak(\"ERROR: Unable to convert a piddle of type %d to S-Lang\",\n" .
	      "pdl->datatype); }\n" );
  $fh->close;

  return " " . &PDL::Core::Dev::PDL_INCLUDE;

} # sub: add_pdl_support()

## User options

my $gdb   = "";
my $debug = 0;
my $help  = 0;
my $slangdir = "";
my $slanglib = "";
my $slanginc = "";
my $pdl = 1;
my $wall = 0;

## Search path for the S-Lang library

# what directories do we search in for S-Lang?
# (we force the CIAO installation to be first in the list to
#  make it less work if multiple matches are found)
#
my @guess_path =
  (
   [ File::Spec->rootdir(), "usr" ],
   [ File::Spec->rootdir(), "usr", "local" ]
  );

# provide support for CIAO 3.0 users
# (can assume a UNIX-style filesystem)
#
if ( defined $ENV{"ASCDS_INSTALL"} ) {

    # the location of the S-Lang library/include files has changed
    # between CIAO 2.3 and CIAO 3.0: as we don't support pre 3.0
    # versions of CIAO it is not too much of an issue
    #
    my @words = split / /, `cat $ENV{ASCDS_INSTALL}/VERSION`;
    die "Error: unable to read $ENV{ASCDS_INSTALL}/VERSION\n"
      if $#words == -1;

    # if pre v3.0 then we do not add it to the directory list
    my $major = (split /\./, $words[1])[0];   # / # - to make emacs happy
    if ( $major > 2 ) {

      # assume that the location hasn't changed from 3.0
      my @ciao_path = split( "/", $ENV{"ASCDS_INSTALL"} );

      # ugh - missing slang.h in include/
      unshift @guess_path,
        [ "/", @ciao_path[1..$#ciao_path], "ots", "slang" ];
      print "Found CIAO $words[1] installation at $ENV{\"ASCDS_INSTALL\"}.\n\n";

    }
} # if: defined $ASCDS_INSTALL

## Check the user-supplied options

GetOptions(
	   'gdb:s' => \$gdb,
	   'debug' => \$debug,
	   'help!' => \$help,
	   'slangdir:s' => \$slangdir,
	   'slanglib:s' => \$slanglib,
	   'slanginc:s' => \$slanginc,
	   'pdl!' => \$pdl,
	   'wall!' => \$wall,
	   ) or usage();

usage() if $help;

#============================================================================
# What S-Lang interpreter are we going to embed?
#============================================================================

# is this the correct thing to do?
my $ext_stat = $Config{_a};
my $ext_dyn  = "." . $Config{so};

my $libname_stat = "libslang$ext_stat";
my $libname_dyn  = "libslang$ext_dyn";
my $incname      = "slang.h";

# try looking through a set of directories
# - note we're assuming a UNIX filesystem
#
if ( $slangdir ) {
    # not absolutely necessary, but simplifies the logic a bit
    die "Error: -slangdir and -slanglib/inc are mutually exclusive options\n"
	if $slanglib ne "" or $slanginc ne "";

    $slanglib = File::Spec->catdir( $slangdir, "lib" );
    $slanginc = File::Spec->catdir( $slangdir, "include" );

} elsif ( !$slanglib && !$slanginc ) {

    # try and guess the location
    print "Guessing location of S-Lang:\n";
    my @matches;
    foreach my $path ( @guess_path ) {
	push @matches, $path if
	  -e File::Spec->catfile( @$path, "include", $incname ) &&
	    (-e File::Spec->catfile( @$path, "lib", $libname_stat ) ||
	     -e File::Spec->catfile( @$path, "lib", $libname_dyn));
    }
    die "Error: unable to find the S-Lang library/include files\n" .
	"       ($libname_stat/$ext_dyn and $incname)\n" .
	"       in any of the following directories:\n" .
	"       " . join(' ', map { File::Spec->catdir(@$_); } @guess_path ) . "\n"
	    if $#matches == -1;

    if ( $#matches > 0 ) {
	print "\nS-Lang was found in the following locations:\n";
	my $num = 1;
        printf "\t%d - %s\n", $num++, File::Spec->catdir(@$_)
	    for @matches;
	print "\n";
	my $sel = prompt("Use which?", '1');
	$sel = $matches[$sel-1] if $sel =~ /^\d+$/;
	$slangdir = $sel;
    } else {
	$slangdir = $matches[0];
    }
    $slangdir = File::Spec->catdir( @$slangdir );
    print "  -> Using $slangdir as the location of S-Lang\n\n";
    $slanglib = File::Spec->catdir( $slangdir, "lib" );
    $slanginc = File::Spec->catdir( $slangdir, "include" );

} elsif ( !$slanginc || !$slanglib ) {
    die "Error: -slanginc and -slanglib must both be specified\n";
}

# can we find the necessary files?
#
die "Error: unable to find slang.h in the include directory ($slanginc)\n"
    unless -e File::Spec->catfile( $slanginc, "slang.h" );
die "Error: unable to find libslang[$ext_dyn|$ext_stat] in the library directory ($slanglib)\n"
  unless -e File::Spec->catfile( $slanglib, $libname_dyn ) ||
         -e File::Spec->catfile( $slanglib, $libname_stat );

my $incpath = "-I$slanginc";
my $libpath = "-L$slanglib -lslang";

# now check the S-Lang interpreter for how it was
# compiled (and some information about type synonyms)
# This information could be checked for at compile time
# - ie whenever a piece of code is first evaluated - but
# let's try and save a little time
# We also check for whether support for float & complex
# types are available - we currently die if they aren't,
# although the code could be updated to make such support
# optional (I don't have the time/interest)
#

    my $config_code = <<'EOC';

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "slang.h"

#define NCHECKS 2
static char *inchecks[NCHECKS] = {
  "Float_Type", "Complex_Type"
};

/* stop loads of messages to the screen */
static void _sl_error_handler( char *emsg ) { return; }

/*
 * Note:
 *   I am not too bothered about memory loses here, since these are
 *   one-shot routines (i.e. called only once)
 */

/*
 * checks that the version of the S-Lang library installed on the
 * system meets the needs of Inline::SLang
 *   a) version check
 *   b) a check that we can use the import functionality of S-Lang
 *      this is not strictly required, but I need this so I include
 *      it for now
 *   c) includes support for floating-point and complex numbers
 *
 * Returns the value 0 on success, otherwise a
 * text string describing the problem
 */
void init_checks( int minver ) {

  static char slstring[30];
  int i;

  /* set up the stack ready for returning values */
  Inline_Stack_Vars;
  Inline_Stack_Reset;

  /*
   * could check against SLANG_VERSION from slang.h
   * but am having "fun" with this approach on my gentoo box, so
   * we use the version from the library itself
   */
  if ( minver > SLang_Version ) {
    Inline_Stack_Push(
      newSVpvf(
        "ERROR: S-Lang version (%d) is less than the minimum rquired for Inline::SLang (%d)",
	SLang_Version, minver
      )
    );
    Inline_Stack_Done;
    return;
  }

  /*
   * initialise the S-Lang interpreter. This is needed since we use
   * the SLang_load_string() routine in both this and
   * sl_type_conversion().
   *
   * We check we can initialise the support for the import command
   * and the init_array_extra routines. These are not necessary for
   * this bit of code, but this is how we will start up the S-Lang
   * interpreter when Inline::SLang is being used, so this acts as
   * a good test. The SLang_init_array_extra() routine was added in
   * version 1.4.7 of S-Lang.
   */
  if( (-1 == SLang_init_all()) ||
      (-1 == SLang_init_array_extra()) ||
      (-1 == SLang_init_import()) ) {
    Inline_Stack_Push(
      newSVpv( "ERROR: Unable to initialize the S-Lang library.", 0 )
    );
    Inline_Stack_Done;
    return;
  }

  /* install an error handler */
  SLang_Error_Hook = _sl_error_handler;

  /* does S-Lang support for floats and complex numbers ? */
  for ( i = 0; i < NCHECKS; i++ ) {
    sprintf( slstring, "%s;", inchecks[i] );
    if ( -1 == SLang_load_string( slstring ) ) {
      Inline_Stack_Push(
        newSVpvf(
	  "ERROR: Your S-Lang library does not support %s type variables.",
	  inchecks[i]
        )
      );
      Inline_Stack_Done;
      return;
    }
  }  /* for: i < NCHECKS */

  /* if we have got this far then we have success, so return 0 */
  Inline_Stack_Push( newSViv(0) );
  Inline_Stack_Done;

} /* init_checks */

/*
 * find out the type conversions for the S-Lang
 * data types. Returns a string or NULL if the
 * type is not recognised.
 */
void sl_type_conversion( char *intype ) {

  static char slstring[30];
  char *outtype;

  /* set up the stack ready for returning values */
  Inline_Stack_Vars;
  Inline_Stack_Reset;

  sprintf( slstring, "string(%s);", intype );
  if ( -1 != SLang_load_string( slstring ) ) {
    (void) SLang_pop_slstring( &outtype );
    Inline_Stack_Push( newSVpv( outtype, 0 ) );
    SLang_free_slstring( outtype );

  } else {
    SLang_restart(1);
    SLang_Error = 0;
    Inline_Stack_Push( &PL_sv_undef );
  }

  Inline_Stack_Done;

} /* sl_type_conversion() */
EOC

#
# I would have preferred to put the C code at the end of the file
# BUT we do not know the library/include file location until
# this point, which means we can not just say 'use Inline C...'
# but have to go this route.
#
# An alternative would have been to do everything in BEGIN blocks,
# but that would have required more substantial changes to the
# code
#
eval( 
    "use Inline C => Config => INC => '$incpath' => LIBS => '$libpath';
          Inline->bind( C => '$config_code' );"
) or die "Error whilst using Inline::C to build the configuration code:\n $@\n";

my $retval = init_checks( 10407 );
die "$retval\n" unless $retval eq "0";

parse_slconfig;

#
# HACK
#
# We do not use any more functions bound by Inline::C, so we can now
# delete the config file to allow the tests to work
# This is ugly - is there a better way (have emailed the inline list
# about it)?
#
my $inline_dir = Inline::find_temp_dir();
die "Error: Inline::find_temp_dir() returned nothing\n"
    unless defined $inline_dir;
die "Error: Inline::find_temp_dir() returned a directory that does not exist:\n" .
    " -> $inline_dir\n\n"
    unless -d $inline_dir;
my $config_file = File::Spec->catfile( $inline_dir, "config" );
die "Error: unable to find the config file - expected it to be:\n" .
    " -< $config_file\n\n"
    unless -e $config_file;
unlink( $config_file )
    or die "Error: unable to delete Inline config file:\n" .
    " -> $config_file\n\n";


#============================================================================
# PDL support
#============================================================================

my $pdlinc = add_pdl_support if $pdl;
$pdl = 0 if $pdlinc eq "";

#============================================================================
# Finalize, and write the makefile
#============================================================================

my $defs = "-DI_SL_HAVE_PDL=$pdl";
$defs .= " -DI_SL_DEBUG" if $debug;

WriteMakefile(
    $defs         ? (DEFINE => $defs) : (),
    defined $gdb  ? (OPTIMIZE => debug_flag()) : (),
    $wall         ? (CCFLAGS => "-Wall") : (),

    INC			=> $incpath . $pdlinc,
    LIBS		=> $libpath,
    OBJECT 		=> 'SLang.o util.o sl2pl.o pl2sl.o' .
	      ($pdl ? ' pdl.o' : ''),
    NAME		=> 'Inline::SLang',
    VERSION_FROM	=> 'SLang.pm', # finds $VERSION
    PREREQ_PM		=> {
			    'Inline' => 0.42,
                            # not really needed but am too lazy to code around
			    'Test::More' => 0,
			   },
    realclean		=> {
			    # _Inline is a directory *NOT* a file,
			    # how can we tell 'realclean' this?
			    FILES => '_Inline stf.h'
			    . ( $pdl ? ' topdl.h toslang.h' : '') },
    ABSTRACT_FROM       => 'SLang.pod',
    AUTHOR              => 'Doug Burke <djburke@cpan.org>',
);

sub debug_flag {
    return $gdb if $gdb;
    $Config{osname} eq 'MSWin32' ? return '-Zi' : return '-g';
}

sub usage {
    my $paths = join( '', map { "\t" . File::Spec->catfile(@$_) . "\n" } @guess_path );

    print <<"END";
Options:
  general:
    --help       this output

  location of the S-Lang library & include files:
    --slangdir=x  looks in x/lib/ and x/include/
   or
    --slanginc=x  location of the S-Lang include file
    --slanglib=x  location of the S-Lang library

   otherwise the following directories are searched:
$paths
  PDL support:
    --pdl     Turn on support for PDL if installed (default)
    --nopdl   Turn off support for PDL

  debugging:
    --gdb     Turn on compiler's debugging flag (use my guess).
    --gdb=x   Pass your own debugging flag, not mine.
    --debug   Turn on diagnostic print statements (a *lot* of
              screen output)

    --wall    Compile with "-Wall"
    --nowall  Do not compile with "-Wall" (default)

END

    exit 0;
}

# end of Makefile.PL