#
# $Header: /cvsroot/gtk2-perl/gtk2-perl-xs/ExtUtils-Depends/lib/ExtUtils/Depends.pm,v 1.6 2003/11/21 06:36:48 muppetman Exp $
#

package ExtUtils::Depends;
use File::Spec;
use File::Basename;
use Carp;
use Cwd;
use IO::File;
use strict;
use vars qw($AUTOLOAD $VERSION);

$VERSION = 0.104;

sub new {
	my ($class, $package, @depends) = @_;
	my $self = {
		_name_ => $package,
		_depends_ => [@depends],
		_dtypemaps_ => [],
		_ddefines_ => [],
		_prefix_ => 'xs/',
		_install_ => [qw(typemaps defs)],
		_handler_ => {
			typemaps => \&basename,
			defs => \&basename,
			headers => sub {
				my $s = $_[0]; 
				$s =~ s(^[^<"]+/)(); #"
				return $s;
			},
		},
	};
	$class = ref($class) || $class;

	$self = bless $self, $class;

	$self->load(@depends) if @depends;
	return $self;
}

sub installdir {
	my $self = shift;
	my $dir = $self->{_name_};
	$dir =~ s/^(\w+::)+//;
	$dir =~ s(::)(/); #/
	$dir = '$(INST_ARCHLIBDIR)/'.$dir.'/Install/';
	return $dir;
}

sub set_prefix {
	my ($self, $prefix) = @_;
	$self->{_prefix_} = $prefix;
}

sub set_libs {
	my ($self, $libs) = @_;
	chomp($libs);
	$self->{_libs_} = $libs;
}

sub set_inc {
	my ($self, $inc) = @_;
	chomp($inc);
	$self->{_inc_} = $inc;
}

# load dependencies ...
sub load {
	my ($self, @depends) = @_;
	my ($dir, $module);
	
	for (@depends) {
		no strict 'refs';
		my ($name, $file);
		undef $dir;
		if (ref $_) {
			($name, $file) = %$_;
			my $dname = $name;
			my $i = 2;
			my $tmpdir = $file;
			$dname =~ s(::)(/); #/
			while ($i--) {
				$tmpdir = dirname($tmpdir);
				if ( -f "$tmpdir/Makefile.PL") {
					$dir = "$tmpdir/blib/lib/".$dname."/Install/";
					last;
				}
			}
		} else {
			$_.='::Install::Files';
			$module = $_.'.pm';
			$module =~ s(::)(/)g; #/
			$name = $_;
			$file = $module;
		}
		eval {require $file;};
		if ($@) {
			die "Cannot load $_: $@\n";
		}
		$dir ||= ${"${_}::CORE"} || $INC{$file};
		$dir = cwd().'/'.$dir
			unless File::Spec->file_name_is_absolute($dir);
		warn "Found $name in $dir\n";
		push @{$self->{_dtypemaps_}}, map {$dir.'/'.$_} @{"${_}::typemaps"};
		#push @{$self->{_ddefs_}}, @{"${_}::defs"};
		push @{$self->{_ddefines_}}, @{"${_}::defines"};
		$self->{_dlibs_} .= " ".${"${_}::libs"};
		$self->{_dinc_} .= " -I$dir ".${"${_}::inc"};
	}
}

sub add_pm {
	my ($self, %pm) = @_;
	foreach (keys %pm) {
		$self->{_pm_}->{$_} = $pm{$_};
	}
}

sub get_pm {
	my $self = shift;
	# maybe make a copy
	foreach ($self->get_headers) {
		next unless s/^"//;
		s/"$//;
		warn "FORCE installing header: $_\n";
		$self->{_pm_}->{$_} = $self->installdir().basename($_);
	}
	foreach ($self->get_typemaps) {
		$self->{_pm_}->{$_} = $self->installdir().basename($_);
	}
	return $self->{_pm_};
}

sub install {
	my $self = shift;
	my $dir = $self->installdir;
	foreach (@_) {
		$self->add_pm($_, $dir.basename($_));
	}
}

# called by AUTOLOAD
sub real_add {
	my ($self, $tag, @args) = @_;
	my $file;
	
	foreach (@args) {
		$file = $self->{_prefix_}.$_;
		if (-e || ! -e $file) {
			$file = $_;
		}
		$self->{$tag}->{$file} = $self->{_counter_}++;
	}
}

# called by AUTOLOAD
sub real_remove {
	my ($self, $tag, @args) = @_;
	my $file;
	
	foreach (@args) {
		$file = $self->{_prefix_}.$_;
		if (-e || ! -e $file) {
			$file = $_;
		}
		delete $self->{$tag}->{$file};
	}
}

# called by AUTOLOAD
sub real_get {
	my ($self, $tag) = @_;

	return sort {$self->{$tag}->{$a} <=> $self->{$tag}->{$b}} 
		keys %{$self->{$tag}};
}

# handle op_foo, where op is get, add, or remove, and foo is one of the
# internal keys.
sub AUTOLOAD {
	my $self = shift @_;
	my $method = $AUTOLOAD;
	my $tag;
	
	$method =~ s/^.*:://;
	if ($method =~ s/^(get|add|remove)_//) {
		no strict 'subs';
		$tag = $method;
		$method = "real_$1";
		return $self->$method ($tag, @_);
	}
	carp "No method '$method'\n";
}

sub DESTROY {}

sub sort_libs {
	my ($libs) = '';
	my (@libs, %seenlibs, @revlibs, @lflags);
	
	foreach (@_) {
		$libs .= ' ';
		$libs .= $_ if( defined($_) );
	}
	$libs =~ s/(^|\s)-[rR]\S+//g;
	
	@libs = split(/\s+/, $libs);
	%seenlibs = ();
	@revlibs=();
	@lflags=();

	foreach (@libs) {
		if (/^-l/) {
			unshift(@revlibs, $_);
		} else {
			unshift(@lflags, $_) unless $seenlibs{$_}++;
		}
	}
	@libs=();
	foreach (@revlibs) {
		unshift(@libs, $_) unless $seenlibs{$_}++;
	}
	return join(' ', @lflags, @libs);
}

sub get_makefile_vars {
	my $self = shift;
	my (%result);
	my ($xfiles, $object, $ldfrom, $clean) = $self->setup_xs();

	push @$clean, keys %{$self->{clean}};
	$result{PM} = $self->get_pm;
	$result{TYPEMAPS} = [@{$self->{_dtypemaps_}}, $self->get_typemaps];
	$result{DEFINE} = join(' ', @{$self->{_ddefines_}}, keys %{$self->{defines}});
	$result{OBJECT} = $object;
	#$result{LDFROM} = $ldfrom;
	$result{XS} = $xfiles;
	$self->{_dinc_} = '' unless( defined($self->{_dinc_}) );
	$self->{_inc_} = '' unless( defined($self->{_inc_}) );
	$result{INC} = join(' ', $self->{_dinc_}, $self->{_inc_});
	$result{LIBS} = [sort_libs($self->{_dlibs_}, $self->{_libs_})];
	$result{clean} = {FILES => join(' ', @$clean) };
	
	return %result;
}

sub setup_xs {
	my $self = shift;
	my $xfiles = {};
	my ($ldfrom, $object, @clean);
	
	$ldfrom = $object = '';
	foreach (keys %{$self->{xs}}) {
		my($xs) = $_;
		s/\.xs$/.c/;
		$xfiles->{$xs} = $_;
		push(@clean, $_);
		s/\.c$/.o/;
		push(@clean, $_);
		$object .= " $_";
		s(.*/)();
		$ldfrom .= " $_";
	}
	foreach (keys %{$self->{c}}) {
		s/\.c$/.o/i;
		push(@clean, $_);
		$object .= " $_";
	}
	return ($xfiles, $object, $ldfrom, [@clean]);
}

sub save_config {
	my ($self, $filename) = @_;
	my ($file, $mdir, $mdir2, $pm, $name);
	my (%installable, %handler);

	@installable{@{$self->{_install_}}} = ();
	%handler = %{$self->{_handler_}};

	$file = new IO::File ">$filename" || croak "Cannot open '$filename': $!";;
	$name = $mdir = $mdir2 = $self->{_name_};
	$pm = $self->{_pm_};
	
	$mdir =~ s/.*:://;
	$mdir2 =~ s.::./.g;
	
	$pm->{$filename} = '$(INST_ARCHLIBDIR)/'."$mdir/Install/Files.pm";

	print $file "#!/usr/bin/perl\n\npackage ${name}::Install::Files;\n\n";

	foreach my $tag (sort keys %{$self}) {
		next if $tag =~ /^_/;
		my %items = %{$self->{$tag}};
		print $file "\@$tag = qw(\n";
		foreach my $item (sort {$items{$a} <=> $items{$b}} keys %items) {
			my $s = exists $handler{$tag} ? $handler{$tag}->($item): $item;
			print $file "\t$s\n";
			$pm->{$item} = '$(INST_ARCHLIBDIR)/'. "$mdir/Install/" . $s if exists $installable{$tag};
		}
		print $file ");\n\n";
	}
	print $file "\$libs = '$self->{_libs_}';\n";
	print $file "\$inc = '$self->{_inc_}';\n";
	print $file <<"EOT";

	\$CORE = undef;
	foreach (\@INC) {
		if ( -f \$_ . "/$mdir2/Install/Files.pm") {
			\$CORE = \$_ . "/$mdir2/Install/";
			last;
		}
	}

	1;
EOT
	close($file);
}

sub write_ext {
	my ($self, $filename) = @_;
	my $file = new IO::File "> $filename" || carp "Cannot create $filename: $!";
	
	print $file "\n\n# Do not edit this file, as it is automatically generated by Makefile.PL\n\n";

	print $file "BOOT:\n{\n";

	foreach ($self->get_boot) {
        my($b) = $_;
        $b =~ s/::/__/g;
        $b = "boot_$b";
        print $file "extern void $b(CV *cv);\n";
	}
	foreach ($self->get_boot) {
        my($b) = $_;
        $b =~ s/::/__/g;
        $b = "boot_$b";
        print $file "callXS($b, cv, mark);\n";
	}

	print $file "}\n";
	close($file);

}

=head1 NAME

ExtUtils::Depends - Easily build XS extensions that depend on XS extensions

=head1 SYNOPSIS

	use ExtUtils::Depends;
	$package = new ExtUtils::Depends ('pkg::name', 'base::package')
	# set the flags and libraries to compile and link the module
	$package->set_inc("-I/opt/blahblah");
	$package->set_lib("-lmylib");
	# add a .c and an .xs file to compile
	$package->add_c('code.c');
	$package->add_xs('module-code.xs');
	# add the typemaps to use
	$package->add_typemaps("typemap");
	# save the info
	$package->save_config('Files.pm');

	WriteMakefile(
		'NAME' => 'Mymodule',
		$package->get_makefile_vars()
	);

=head1 DESCRIPTION

This module tries to make it easy to build Perl extensions that use
functions and typemaps provided by other perl extensions. This means
that a perl extension is treated like a shared library that provides
also a C and an XS interface besides the perl one.
This works as long as the base extension is loaded with the RTLD_GLOBAL
flag (usually done with a 

	sub dl_load_flags {0x01}

in the main .pm file) if you need to use functions defined in the module.

=head1 SEE ALSO

ExtUtils::MakeMaker.

=head1 AUTHOR

Paolo Molaro, lupus@debian.org

=head1 MAINTAINER

The Gtk2 project, http://gtk2-perl.sf.net/

=head1 LICENSE

This library is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.

=cut

