#!/usr/bin/perl -w

use strict;

use Getopt::Long;
use File::Basename;
use Cwd;

do "sanity.pl";

my $CoreBuild = -d "ext" && -f "perl.h" && -d "symbian" && -f "perl.c";

my $SymbianVersion;

if (exists $ENV{EPOCROOT}) {
    if ($ENV{EPOCROOT} =~ m!\\Symbian\\UIQ_21\\$!i) {
	$SymbianVersion = '7.0s'; # TODO: other UIQ versions
    } elsif ($ENV{EPOCROOT} =~ m!\\Symbian\\(.+?)\\!i) {
	$SymbianVersion = $1;
    }
}

$SymbianVersion = $ENV{XSBUILD_SYMBIAN_VERSION}
  if exists $ENV{XSBUILD_SYMBIAN_VERSION};

my $PerlVersion    = $ENV{XSBUILD_PERL_VERSION};
my $CSuffix        = '.c';
my $CPlusPlus;
my $Config;
my $Build;
my $Clean;
my $DistClean;
my $Sis;

sub usage {
    die <<__EOF__;
$0: Usage: $0 [--symbian=version] [--perl=version]
              [--extversion=x.y]
              [--csuffix=csuffix] [--cplusplus|--cpp]
              [--win=win] [--arm=arm]
              [--config|--build|--clean|--distclean|--sis] ext
__EOF__
}

my $CWD;
my $SDK;
my $VERSION;
my $R_V_SV;
my $PERLSDK;
my $WIN = 'wins';
my $ARM = 'thumb';
my $BUILDROOT = getcwd();

if ( !defined $PerlVersion && $0 =~ m:\\symbian\\perl\\(.+)\\bin\\xsbuild.pl:i )
{
    $PerlVersion = $1;
}

if ( !defined $SymbianVersion) {
    ($SymbianVersion) = ($ENV{PATH} =~ m!\\Symbian\\(.+?)\\!i);
}

my ($SYMBIAN_ROOT, $SYMBIAN_VERSION, $SDK_NAME, $SDK_VARIANT, $SDK_VERSION);

if ($CoreBuild) {
    unshift @INC, "symbian";
    do "sanity.pl";
    my %VERSION = %{ do "version.pl" };
    ($SYMBIAN_ROOT, $SYMBIAN_VERSION, $SDK_NAME, $SDK_VARIANT, $SDK_VERSION) =
      @{ do "sdk.pl" };
    $VERSION = "$VERSION{REVISION}$VERSION{VERSION}$VERSION{SUBVERSION}";
    $R_V_SV  = "$VERSION{REVISION}.$VERSION{VERSION}.$VERSION{SUBVERSION}";
    $BUILDROOT    = do "cwd.pl";
    $PerlVersion    = $R_V_SV;
}

my %CONF;

usage()
  unless GetOptions(
    'symbian=s'     => \$SymbianVersion,
    'perl=s'        => \$PerlVersion,
    'extversion=s'  => \$CONF{EXTVERSION},
    'csuffix=s'     => \$CSuffix,
    'cplusplus|cpp' => \$CPlusPlus,
    'win=s'         => \$WIN,
    'arm=s'         => \$ARM,
    'config'        => \$Config,
    'build'         => \$Build,
    'clean'         => \$Clean,
    'distclean'     => \$DistClean,
    'sis'           => \$Sis
  );

usage() unless @ARGV;

$CSuffix = '.cpp' if $CPlusPlus;
$Build = !( $Config || $Clean || $DistClean ) || $Sis unless defined $Build;

die "$0: Symbian version undefined\n" unless defined $SymbianVersion;

$SymbianVersion =~ s:/:\\:g;

die "$0: Symbian version '$SymbianVersion' not found\n"
  unless -d "\\Symbian\\$SymbianVersion";

die "$0: Perl version undefined\n" unless defined $PerlVersion;

die "$0: Perl version '$PerlVersion' not found\n"
  if !$CoreBuild && !-d "\\Symbian\\Perl\\$PerlVersion";

print "Configuring with Symbian $SymbianVersion and Perl $PerlVersion...\n";

$PERLSDK = "\\Symbian\\Perl\\$PerlVersion";

$R_V_SV = $PerlVersion;

$VERSION = $PerlVersion unless defined $VERSION;

$VERSION =~ tr/.//d if defined $VERSION;

$ENV{SDK}   = $SYMBIAN_ROOT;    # For the Errno extension
$ENV{CROSS} = 1;                # For the Encode extension (unbuilt now)

my $UARM = 'urel';
my $UREL = "$SYMBIAN_ROOT\\epoc32\\release\\-ARM-\\$UARM";
my $SRCDBG;
if (exists $ENV{UREL}) {
    $UREL = $ENV{UREL}; # from sdk.pl
    $UREL =~ s/-ARM-/$ARM/;
    $UARM = $ENV{UARM}; # from sdk.pl
    $SRCDBG = $UARM eq 'udeb' ? "SRCDBG" : "";
}

my %EXTCFG;

sub write_bld_inf {
    my ($base) = @_;
    print "\tbld.inf\n";
    open( BLD_INF, ">bld.inf" ) or die "$0: bld.inf: $!\n";
    print BLD_INF <<__EOF__;
PRJ_MMPFILES
$base.mmp
PRJ_PLATFORMS
$WIN $ARM
__EOF__
    close(BLD_INF);
}

sub system_echo {
    my $cmd = shift;
    print "xsbuild: ", $cmd, "\n";
    return system($cmd);
}

sub run_PL {
    my ( $PL, $dir, $file ) = @_;
    if ( defined $file ) {
        print "\t(Running $dir\\$PL to create $file)\n";
        unlink($file);
    }
    else {
        print "\t(Running $dir\\$PL)\n";
    }
    my $cmd;
    if ($CoreBuild) {
        # Problem: the Config.pm we have in $BUILDROOT\\lib carries the
        # version number of the Perl we are building, while the Perl
        # we are running might have some other version.  Solution:
        # temporarily replace the Config.pm with a patched version.
        my $V = sprintf "%vd", $^V;
        unlink("$BUILDROOT\\lib\\Config.pm.bak");
	print "(patching $BUILDROOT\\lib\\Config.pm)\n";
	system_echo("perl -pi.bak -e \"s:\\Q$R_V_SV:$V:\" $BUILDROOT\\lib\\Config.pm");
    }
    system_echo("perl -I$BUILDROOT\\lib -I$BUILDROOT\\xlib\\symbian -I$BUILDROOT\\t\\lib $PL") == 0
      or warn "$0: $PL failed.\n";
    if ($CoreBuild) {
        system_echo("copy $BUILDROOT\\lib\\Config.pm.bak $BUILDROOT\\lib\\Config.pm");
    }
    if ( defined $file ) { -s $file or die "$0: No $file created.\n" }
}

sub read_old_multi {
    my ( $conf, $k ) = @_;
    push @{ $conf->{$k} }, split( ' ', $1 ) if /^$k\s(.+)$/;
}

sub uniquefy_filenames {
    my $b = [];
    my %c = ();
    for my $i (@{$_[0]}) {
        $i =~ s!/!\\!g;
        $i = lc $i if $i =~ m!\\!;
        $i =~ s!^c:!!;
        push @$b, $i unless $c{$i}++;
    }
    return $b;
}

sub read_mmp {
    my ( $conf, $mmp ) = @_;
    if ( -r $mmp && open( MMP, "<$mmp" ) ) {
        print "\tReading $mmp...\n";
        while (<MMP>) {
            chomp;
            $conf->{TARGET}     = $1 if /^TARGET\s+(.+)$/;
            $conf->{TARGETPATH} = $1 if /^TARGETPATH\s+(.+)$/;
            $conf->{EXTVERSION} = $1 if /^EXTVERSION\s+(.+)$/;
            read_old_multi( $conf, "SOURCE" );
            read_old_multi( $conf, "SOURCEPATH" );
            read_old_multi( $conf, "USERINCLUDE" );
            read_old_multi( $conf, "SYSTEMINCLUDE" );
            read_old_multi( $conf, "LIBRARY" );
            read_old_multi( $conf, "MACRO" );
        }
        close(MMP);
    }
}

sub write_mmp {
    my ( $ext, $base, $userinclude, @src ) = @_;

    my $extdash = $ext; $extdash =~ s!\\!-!g;

    print "\t$base.mmp\n";
    $CONF{TARGET}        = "perl$VERSION-$extdash.dll";
    $CONF{TARGETPATH}    = "\\System\\Libs\\Perl\\$R_V_SV";
    $CONF{SOURCE}        = [@src];
    $CONF{SOURCEPATH}    = [ $CWD, $BUILDROOT ];
    $CONF{USERINCLUDE}   = [ $CWD, $BUILDROOT ];
    $CONF{SYSTEMINCLUDE} = ["$PERLSDK\\include"] unless $CoreBuild;
    $CONF{SYSTEMINCLUDE} = [ $BUILDROOT, "$BUILDROOT\\symbian" ] if $CoreBuild;
    $CONF{LIBRARY}       = [];
    $CONF{MACRO}         = [];
    read_mmp( \%CONF, "_init.mmp" );
    read_mmp( \%CONF, "$base.mmp" );

    if ($base eq 'Zlib') {
	push @{$CONF{USERINCLUDE}}, "$CWD\\zlib-src";
    }

    for my $ui ( @{$userinclude} ) {
        $ui =~ s!/!\\!g;
        if ( $ui =~ m!^(?:[CD]:)?\\! ) {
            push @{ $CONF{USERINCLUDE} }, $ui;
        }
        else {
            push @{ $CONF{USERINCLUDE} }, "$BUILDROOT\\$ui";
        }
    }
    push @{ $CONF{SYSTEMINCLUDE} }, "\\epoc32\\include";
    push @{ $CONF{SYSTEMINCLUDE} }, "\\epoc32\\include\\libc";
    push @{ $CONF{LIBRARY} },       "euser.lib";
    push @{ $CONF{LIBRARY} },       "estlib.lib";
    push @{ $CONF{LIBRARY} },       "perl$VERSION.lib";
    push @{ $CONF{MACRO} },         "SYMBIAN" unless $CoreBuild;
    push @{ $CONF{MACRO} },         "PERL_EXT" if $CoreBuild;
    push @{ $CONF{MACRO} },         "MULTIPLICITY";
    push @{ $CONF{MACRO} },         "PERL_IMPLICIT_CONTEXT";
    push @{ $CONF{MACRO} },         "PERL_GLOBAL_STRUCT";
    push @{ $CONF{MACRO} },         "PERL_GLOBAL_STRUCT_PRIVATE";

    if ($SDK_VARIANT eq 'S60') {
      push @{ $CONF{MACRO} }, '__SERIES60__'
	unless grep { $_ eq '__SERIES60__' } @{ $CONF{MACRO} };
    }
    if ($SDK_VARIANT eq 'S80') {
      push @{ $CONF{MACRO} }, '__SERIES80__'
	unless grep { $_ eq '__SERIES80__' } @{ $CONF{MACRO} };
    }
    if ($SDK_VARIANT eq 'UIQ') {
      push @{ $CONF{MACRO} }, '__UIQ__'
	unless grep { $_ eq '__UIQ__' } @{ $CONF{MACRO} };
    }

    for my $u (qw(SOURCE SOURCEPATH SYSTEMINCLUDE USERINCLUDE LIBRARY MACRO)) {
        $CONF{$u} = uniquefy_filenames( $CONF{$u} );
    }
    open( BASE_MMP, ">$base.mmp" ) or die "$0: $base.mmp: $!\n";

    print BASE_MMP <<__EOF__;
TARGET		$CONF{TARGET}
TARGETTYPE	dll
TARGETPATH	$CONF{TARGETPATH}
SOURCE		@{$CONF{SOURCE}}
$SRCDBG
__EOF__
    for my $u (qw(SOURCEPATH SYSTEMINCLUDE USERINCLUDE)) {
        for my $v ( @{ $CONF{$u} } ) {
            print BASE_MMP "$u\t$v\n";
        }
    }
    # OPTION does not work in MMPs for pre-2.0 SDKs?
    print BASE_MMP <<__EOF__;
LIBRARY		@{$CONF{LIBRARY}}
MACRO		@{$CONF{MACRO}}
// OPTION	MSVC /P // Uncomment for creating .i (cpp'ed .cpp)
// OPTION	GCC -E  // Uncomment for creating .i (cpp'ed .cpp)
__EOF__
#    if (-f "$base.rss") {
#        print BASE_MMP "RESOURCE\t$base.rss\n";
#    }
    close(BASE_MMP);

}

sub write_makefile {
    my ( $base, $build ) = @_;

    print "\tMakefile\n";

    my $windef1 = "$SYMBIAN_ROOT\\Epoc32\\Build$CWD\\$base\\$WIN\\$base.def";
    my $windef2 = "..\\BWINS\\${base}u.def";
    my $armdef1 = "$SYMBIAN_ROOT\\Epoc32\\Build$CWD\\$base\\$ARM\\$base.def";
    my $armdef2 = "..\\BMARM\\${base}u.def";

    my $wrap = $SYMBIAN_ROOT && defined $SDK_VARIANT eq 'S60' && $SDK_VERSION eq '1.2' && $SYMBIAN_ROOT !~ /_CW$/;
    my $ABLD = $wrap ? 'perl b.pl' : 'abld';

    open( MAKEFILE, ">Makefile" ) or die "$0: Makefile: $!\n";
    print MAKEFILE <<__EOF__;
WIN = $WIN
ARM = $ARM
ABLD = $ABLD

all:	build freeze

sis:	build_arm freeze_arm

build:	abld.bat build_win build_arm

abld.bat:
	bldmake bldfiles

build_win: abld.bat
	bldmake bldfiles
	\$(ABLD) build \$(WIN) udeb

build_arm: abld.bat
	bldmake bldfiles
	\$(ABLD) build \$(ARM) $UARM

win:	build_win freeze_win

arm:	build_arm freeze_arm

freeze:	freeze_win freeze_arm

freeze_win:
	bldmake bldfiles
	\$(ABLD) freeze \$(WIN) $base

freeze_arm:
	bldmake bldfiles
	\$(ABLD) freeze \$(ARM) $base

defrost:	defrost_win defrost_arm

defrost_win:
	-del /f $windef1
	-del /f $windef2

defrost_arm:
	-del /f $armdef1
	-del /f $armdef2

clean:	clean_win clean_arm

clean_win:
	\$(ABLD) clean \$(WIN)

clean_arm:
	\$(ABLD) clean \$(ARM)

realclean:	clean realclean_win realclean_arm
	-del /f _init.c b.pl
	-del /f $base.c $base.mmp

realclean_win:
	\$(ABLD) reallyclean \$(WIN)

realclean_arm:
	\$(ABLD) reallyclean \$(ARM)

distclean:	defrost realclean
	-rmdir ..\\BWINS ..\\BMARM
	-del /f const-c.inc const-xs.inc
	-del /f Makefile abld.bat bld.inf
__EOF__
    close(MAKEFILE);
    if ($wrap) {
	if(open(B,">b.pl")) {
	    print B <<'__EOF__';
# abld.pl wrapper.

# nmake doesn't like MFLAGS and MAKEFLAGS being set to -w and w.
delete $ENV{MFLAGS};
delete $ENV{MAKEFLAGS};

print "abld @ARGV\n";
system_echo("abld @ARGV");
__EOF__
            close(B);
	} else {
	    warn "$0: failed to create b.pl: $!\n";
	}
    }
}

sub update_dir {
    print "[chdir from ", getcwd(), " to ";
    chdir(shift) or return;
    update_cwd();
    print getcwd(), "]\n";
}

sub xsconfig {
    my ( $ext, $dir ) = @_;
    print "Configuring for $ext, directory '$dir'...\n";
    my $extu = $CoreBuild ? "$BUILDROOT\\lib\\ExtUtils" : "$PERLSDK\\lib\\ExtUtils";
    update_dir($dir) or die "$0: chdir '$dir': $!\n";
    my $build  = dirname($ext);
    my $base   = basename($ext);
    my $basexs = "$base.xs";
    my $basepm = "$base.pm";
    my $basec  = "$base$CSuffix";
    my $extdir = ".";
    if ( $dir =~ m:^ext\\(.+): ) {
        $extdir = $1;
    }
    elsif ( $dir ne "." ) {
        $extdir = $dir;
    }
    my $extdirdir  = dirname($extdir);
    my $targetroot = "\\System\\Libs\\Perl\\$R_V_SV";
    write_bld_inf($base) if -f $basexs;

    my %src;
    $src{$basec}++;

    $extdirdir = $extdirdir eq "." ? "" : "$extdirdir\\";

    my $extdash = $ext; $extdash =~ s!\\!-!g;

    my %lst;
    $lst{"$UREL\\perl$VERSION-$extdash.dll"} =
      "$targetroot\\$ARM-symbian\\$base.dll"
      if -f $basexs;
    $lst{"$dir\\$base.pm"} = "$targetroot\\$extdirdir$base.pm"
      if -f $basepm && $base ne 'XSLoader';

    my %incdir;
    my $ran_PL;
    if ( -d 'lib' ) {
        use File::Find;
        my @found;
        find( sub { push @found, $File::Find::name if -f $_ }, 'lib' );
        for my $found (@found) {
	    next if $found =~ /\.bak$/i; # Zlib
            my ($short) = ( $found =~ m/^lib.(.+)/ );
            $short =~ s!/!\\!g;
            $found =~ s!/!\\!g;
            $lst{"$dir\\$found"} = "$targetroot\\$short";
        }
    }
    if ( my @pm = glob("*.pm */*.pm") ) {
        for my $pm (@pm) {
            next if $pm =~ m:^t/:;
            $pm =~ s:/:\\:g;
            $lst{"$dir\\$pm"} = "$targetroot\\$extdirdir$pm";
        }
    }
    if ( my @c = glob("*.c *.cpp */*.c */*.cpp") ) {
	@c = grep { ! m:^zlib-src/: } @c if $ext eq 'ext\Compress\Zlib';
        for my $c (@c) {
            $c =~ s:/:\\:g;
            $src{$c}++;
        }
    }
    if ( my @h = glob("*.h */*.h") ) {
        @h = grep { ! m:^zlib-src/: } @h if $ext eq 'ext\Compress\Zlib';
        for my $h (@h) {
            $h =~ s:/:\\:g;
            $h = dirname($h);
            $incdir{"$dir\\$h"}++ unless $h eq ".";
        }
    }
    if ( exists $EXTCFG{$ext} ) {
        for my $cfg ( @{ $EXTCFG{$ext} } ) {
            if ( $cfg =~ /^([-+])?(.+\.(c|cpp|h))$/ ) {
                my $o = defined $1 ? $1 : '+';
                my $f = $2;
                $f =~ s:/:\\:g;
                for my $f ( glob($f) ) {
                    if ( $o eq '+' ) {
                        warn "$0: no source file $dir\\$f\n" unless -f $f;
                        $src{$f}++ unless $cfg =~ /\.h$/;
                        if ( $f =~ m:^(.+)\\[^\\]+$: ) {
                            $incdir{$1}++;
                        }
                    }
                    elsif ( $o eq '-' ) {
                        delete $src{$f};
                    }
                }
            }
            if ( $cfg =~ /^([-+])?(.+\.(pm|pl|inc))$/ ) {
                my $o = defined $1 ? $1 : '+';
                my $f = $2;
                $f =~ s:/:\\:g;
                for my $f ( glob($f) ) {
                    if ( $o eq '+' ) {
                        warn "$0: no Perl file $dir\\$f\n" unless -f $f;
                        $lst{"$dir\\$f"} = "$targetroot\\$extdir\\$f";
                    }
                    elsif ( $o eq '-' ) {
                        delete $lst{"$dir\\$f"};
                    }
                }
            }
            if ( $cfg eq 'CONST' && !$ran_PL++ ) {
                run_PL( "Makefile.PL", $dir, "const-xs.inc" );
            }
        }
    }
    unless ( $ran_PL++ ) {
        run_PL( "Makefile.PL", $dir ) if -f "Makefile.PL";
    }
    if ( $dir eq "ext\\Errno" ) {
        run_PL( "Errno_pm.PL", $dir, "Errno.pm" );
        $lst{"$dir\\Errno.pm"} = "$targetroot\\Errno.pm";
    }
    elsif ( $dir eq "ext\\Devel\\PPPort" ) {
        run_PL( "ppport_h.PL", $dir, "ppport.h" );
    }
    elsif ( $dir eq "ext\\DynaLoader" ) {
        run_PL( "XSLoader_pm.PL", $dir, "XSLoader.pm" );
        $lst{"ext\\DynaLoader\\XSLoader.pm"} = "$targetroot\\XSLoader.pm";
    }
    elsif ( $dir eq "ext\\Encode" ) {
        system_echo("perl bin\\enc2xs -Q -O -o def_t.c -f def_t.fnm") == 0
          or die "$0: running enc2xs failed: $!\n";
    }

    my @lst = sort keys %lst;

    read_mmp( \%CONF, "_init.mmp" );
    read_mmp( \%CONF, "$base.mmp" );

    if ( -f $basexs ) {
        my %MM;    # MakeMaker results
        my @MM = qw(VERSION XS_VERSION);
        if ( -f "Makefile" ) {
            print "\tReading MakeMaker Makefile...\n";
            if ( open( MAKEFILE, "Makefile" ) ) {
                while (<MAKEFILE>) {
                    for my $m (@MM) {
                        if (m!^$m = (.+)!) {
                            $MM{$m} = $1;
                            print "\t$m = $1\n";
                        }
                    }
                }
                close(MAKEFILE);
            }
            else {
                warn "$0: Makefile: $!";
            }
            print "\tDeleting MakeMaker Makefile.\n";
            unlink("Makefile");
        }

        unlink($basec);
        print "\t$basec\n";
        if ( defined $CONF{EXTVERSION} ) {
            my $EXTVERSION = $CONF{EXTVERSION};
            print "\tUsing $EXTVERSION for version...\n";
            $MM{VERSION} = $MM{XS_VERSION} = $EXTVERSION;
        }
        die "$0: VERSION or XS_VERSION undefined\n"
          unless defined $MM{VERSION} && defined $MM{XS_VERSION};
        if ( open( BASE_C, ">$basec" ) ) {
            print BASE_C <<__EOF__;
#ifndef VERSION
#define VERSION "$MM{VERSION}"
#endif
#ifndef XS_VERSION
#define XS_VERSION "$MM{XS_VERSION}"
#endif
__EOF__
            close(BASE_C);
        }
        else {
            warn "$0: $basec: $!";
        }
        unless (
            system_echo(
"perl -I$BUILDROOT\\lib -I$PERLSDK\\lib $extu\\xsubpp -csuffix .cpp -typemap $extu\\typemap -noprototypes $basexs >> $basec"
            ) == 0
            && -s $basec
          )
        {
            die "$0: perl xsubpp failed: $!\n";
        }

        print "\t_init.c\n";
        open( _INIT_C, ">_init.c" ) or die "$!: _init.c: $!\n";
        print _INIT_C <<__EOF__;
    #include "EXTERN.h"
    #include "perl.h"
    EXPORT_C void _init(void *handle) {
    }
__EOF__
        close(_INIT_C);

        my @src = ( "_init.c", sort keys %src );

        if ( $base eq "Encode" ) {    # Currently unused.
            for my $submf ( glob("*/Makefile") ) {
                my $d = dirname($submf);
                print "Configuring Encode::$d...\n";
                if ( open( SUBMF, $submf ) ) {
                    if ( update_dir($d) ) {
                        my @subsrc;
                        while (<SUBMF>) {
                            next if 1 .. /postamble/;
                            if (m!^(\w+_t)\.c : !) {
                                system_echo(
                                    "perl ..\\bin\\enc2xs -Q -o $1.c -f $1.fnm")
                                  == 0
                                  or warn "$0: enc2xs: $!\n";
                                push @subsrc, "$1.c";
                            }
                        }
                        close(SUBMF);
                        unlink($submf);
                        my $subbase = $d;
                        $subbase =~ s!/!::!g;
                        write_mmp( $ext, $subbase, ["..\\Encode"], "$subbase.c",
                            @subsrc );
                        write_makefile( $subbase, $build );
                        write_bld_inf($subbase);

                        unless (
                            system_echo(
"perl -I$BUILDROOT\\lib ..\\$extu\\xsubpp -csuffix .cpp -typemap ..\\$extu\\typemap -noprototypes $subbase.xs > $subbase.c"
                            ) == 0
                            && -s "$subbase.c"
                          )
                        {
                            die "$0: perl xsubpp failed: $!\n";
                        }
                        update_dir("..");
                    }
                    else {
                        warn "$0: chdir $d: $!\n";
                    }
                }
                else {
                    warn "$0: $submf: $!";
                }
            }
            print "Configuring Encode...\n";
        }

        write_mmp( $ext, $base, [ keys %incdir ], @src );
        write_makefile( $base, $build );
    }
    my $lstname = $ext;
    $lstname =~ s:^ext\\::;
    $lstname =~ s:\\:-:g;
    print "\t$lstname.lst\n";
    my $lstout =
      $CoreBuild ? "$BUILDROOT/symbian/$lstname.lst" : "$BUILDROOT/$lstname.lst";
    if ( open( my $lst, ">$lstout" ) ) {
        for my $f (@lst) { print $lst qq["$f"-"!:$lst{$f}"\n] }
        close($lst);
    }
    else {
        die "$0: $lstout: $!\n";
    }
    update_dir($BUILDROOT);
}

sub update_cwd {
    $CWD = getcwd();
    $CWD =~ s!^[CD]:!!i;
    $CWD =~ s!/!\\!g;
}

for my $ext (@ARGV) {

    $ext =~ s!::!\\!g;
    my $extdash = $ext =~ /ext\\/ ? $ext : "ext\\$ext"; $extdash =~ s!\\!-!g;
    $ext =~ s!/!\\!g;

    my $cfg;

    $cfg = $2 if $ext =~ s/(.+?),(.+)/$1/;

    my $dir;

    unless ( -e $ext ) {
        if ( $ext =~ /\.xs$/ && !-f $ext ) {
            if ( -f "ext\\$ext" ) {
                $ext = "ext\\$ext";
                $dir = dirname($ext);
            }
        }
        elsif ( !-d $ext ) {
            if ( -d "ext\\$ext" ) {
                $ext = "ext\\$ext";
                $dir = $ext;
            }
        }
        $dir = "." unless defined $dir;
    }
    else {
        if ( $ext =~ /\.xs$/ && -f $ext ) {
            $ext = dirname($ext);
            $dir = $ext;
        }
        elsif ( -d $ext ) {
            $dir = $ext;
        }
    }

    if ( $ext eq "XSLoader" ) {
        $ext = "ext\\XSLoader";
    }
    if ( $ext eq "ext\\XSLoader" ) {
        $dir = "ext\\DynaLoader";
    }

    $EXTCFG{$ext} = [ split( /,/, $cfg ) ] if defined $cfg;

    die "$0: no lib\\Config.pm\n"
      if $CoreBuild && $Build && !-f "lib\\Config.pm";

    if ($CoreBuild) {
        open( my $cfg, "symbian/install.cfg" )
          or die "$0: symbian/install.cfg: $!\n";
        my $extdir = $dir;
        $extdir =~ s:^ext\\::;
        while (<$cfg>) {
            next unless /^ext\s+(.+)/;
            chomp;
            my $ext = $1;
            my @ext = split( ' ', $ext );
            $EXTCFG{"ext\\$ext[0]"} = [@ext];
        }
        close($cfg);
    }

    if ( $Config || $Build ) {
        xsconfig( $ext, $dir ) or die "$0: xsconfig '$ext' failed\n";
        next if $Config;
    }

    if ($dir eq ".") {
	warn "$0: No directory for $ext, skipping...\n";
	next;
    }

    my $chdir = $ext eq "ext\\XSLoader" ? "ext\\DynaLoader" : $dir;
    die "$0: no directory '$chdir'\n" unless -d $chdir;
    update_dir($chdir) or die "$0: chdir '$chdir' failed: $!\n";

    my %CONF;

    my @ext   = split( /\\/, $ext );
    my $base  = $ext[-1];

    if ( $Clean || $DistClean ) {
        print "Cleaning $ext...\n";
        unlink("bld.inf");
        unlink("$base.mmp");
        unlink("_init.c");
        unlink("const-c.inc");
        unlink("const-xs.inc");
        rmdir("..\\bmarm");
    }

    if ( $Build && $ext ne "ext\\XSLoader" && $ext ne "ext\\Errno" ) {

     # We compile the extension three (3) times.
     # (1) Only the _init.c to get _init() as the ordinal 1 function in the DLL.
     # (2) With the rest and the _init.c to get ordinals for the rest.
     # (3) With an updated _init.c that carries the symbols from step (2).

        system_echo("make clean");
        system_echo("make defrost") == 0 or warn "$0: make defrost failed\n";

        my @TARGET;

        push @TARGET, 'sis' if $Sis;

        # Compile #1.
        # Hide all but the _init.c.
        print "\n*** $ext - Compile 1 of 3.\n\n";
	print "(patching $base.mmp)\n";
        system(
"perl -pi.bak -e \"s:^SOURCE\\s+_init.c:SOURCE\\t_init.c // :\" $base.mmp"
        );
	system_echo("bldmake bldfiles");
        system_echo("make @TARGET") == 0 or die "$0: make #1 failed\n";

        # Compile #2.
        # Reveal the rest again.
        print "\n*** $ext - Compile 2 of 3.\n\n";
	print "(patching $base.mmp)\n";
        system(
"perl -pi.bak -e \"s:^SOURCE\\t_init.c // :SOURCE\\t_init.c :\" $base.mmp"
        );
        system_echo("make @TARGET") == 0 or die "$0: make #2 failed\n";
        unlink("$base.mmp.bak");

        open( _INIT_C, ">_init.c" ) or die "$0: _init.c: $!\n";
        print _INIT_C <<'__EOF__';
#include "EXTERN.h"
#include "perl.h"

/* This is a different but matching definition from in dl_symbian.xs. */
typedef struct {
    void*	handle;
    int		error;
    HV*		symbols;
} PerlSymbianLibHandle;

EXPORT_C void _init(void* handle) {
__EOF__

        my %symbol;
	my $def;
	my $basef;
        for my $f ("$SYMBIAN_ROOT\\Epoc32\\Build$CWD\\$base\\WINS\\perl$VERSION-$extdash.def",
		   "..\\BMARM\\perl$VERSION-${extdash}u.def") {
	    print "\t($f - ";
	    if ( open( $def, $f ) ) {
		print "OK)\n";
	        $basef = $f;
		last;
	    } else {
		print "no)\n";
	    }
	}
	unless (defined $basef) {
	    die "$0: failed to find .def for $base\n";
	}
        while (<$def>) {
            next while 1 .. /^EXPORTS/;
            if (/^\s*(\w+) \@ (\d+) /) {
                $symbol{$1} = $2;
            }
        }
        close($def);
 
        my @symbol = sort keys %symbol;
        if (@symbol) {
            print _INIT_C <<'__EOF__';
    dTHX;
    PerlSymbianLibHandle* h = (PerlSymbianLibHandle*)handle;
    if (!h->symbols)
        h->symbols = newHV();
    if (h->symbols) {
__EOF__
            for my $sym (@symbol) {
                my $len = length($sym);
                print _INIT_C <<__EOF__;
        hv_store(h->symbols, "$sym", $len, newSViv($symbol{$sym}), 0);
__EOF__
            }
        }
        else {
            die "$0: $basef: no exports found\n";
        }

        print _INIT_C <<'__EOF__';
    }
}
__EOF__
        close(_INIT_C);

        # Compile #3.  This is for real.
        print "\n*** $ext - Compile 3 of 3.\n\n";
        system_echo("make @TARGET") == 0 or die "$0: make #3 failed\n";

    }
    elsif ( $Clean || $DistClean ) {
        if ( $ext eq "ext\\Errno" ) {
            unlink( "Errno.pm", "Makefile" );
        }
        else {
            if ( -f "Makefile" ) {
                if ($Clean) {
                    system_echo("make clean") == 0 or die "$0: make clean failed\n";
                }
                elsif ($DistClean) {
                    system_echo("make distclean") == 0
                      or die "$0: make distclean failed\n";
                }
            }
	    if ( $ext eq "ext\\Compress\\Zlib" ) {
		my @bak;
		find( sub { push @bak, $File::Find::name if /\.bak$/ }, "." );
		unlink(@bak) if @bak;
		my @src;
		find( sub { push @src, $_ if -f $_ }, "zlib-src" );
		unlink(@src) if @src;
		unlink("constants.xs");
	    }
            if ( $ext eq "ext\\Devel\\PPPort" ) {
                unlink("ppport.h");
            }
        }
	my @D = glob("../BMARM/*.def ../BWINS/*.def");
	unlink(@D) if @D;
        my @B = glob("ext/BWINS ext/BMARM ext/*/BWINS ext/*/BMARM Makefile");
        rmdir(@B) if @B;
    }

    update_dir($BUILDROOT);

}    # for my $ext

exit(0);

