#
# Construct file to build scons during development.
# (Kind of ironic that we're using the classic Perl Cons
# to build its Python child...)
#

#
# Copyright (c) 2001 Steven Knight
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

$project = 'scons';

Default qw( . );

#
# An internal "whereis" routine to figure out if we have a
# given program available.  Put it in the "cons::" package
# so subsidiary Conscript files can get at it easily, too.
#
use Config;
sub cons::whereis {
    my $file = shift;
    foreach my $dir (split(/$Config{path_sep}/, $ENV{PATH})) {
	$f = File::Spec->catfile($dir, $file);
	return $f if -x $f && ! -d $f;
    }
    return undef
}

sub cons::read_file {
    my $file = shift;
    open(F, "<$file") || die "cannot open $file: $!";
    my @lines = <F>;
    close(F);
    return wantarray ? @lines : join('', @lines);
}

#
# We let the presence or absence of various utilities determine
# whether or not we bother to build certain pieces of things.
# This will allow people to still do SCons work even if they
# don't have Aegis or RPM installed, for example.
#
$aegis = cons::whereis('aegis');
$aesub = cons::whereis('aesub');
$rpm = cons::whereis('rpm');
$dh_builddeb = cons::whereis('dh_builddeb');
$fakeroot = cons::whereis('fakeroot');

# My installation on Red Hat doesn't like any debhelper version
# beyond 2, so let's use 2 as the default on any non-Debian build.
$DH_COMPAT = (-f "/etc/debian_version") ? 3 : 2;

#
# Now grab the information that we "build" into the files (using sed).
#
chomp($date = $ARG{date});
if (! $date) {
    ($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
    $year += 1900;
    $date = sprintf("%4d/%02d/%02d %02d:%02d:%02d",
		    $year, $mon, $mday, $hour, $min, $sec);
}

$developer = $ARG{developer} || $ENV{USERNAME} || $ENV{LOGNAME} || $ENV{USER};

$revision = $ARG{version};
chomp($revision = `$aesub '\$version' 2>/dev/null`) if $aesub && ! $revision;
$revision = '0.04' if ! $revision;

@arr = split(/\./, $revision);
@arr = ($arr[0], map {length($_) == 1 ? "0$_" : $_} @arr[1 .. $#arr]);
$revision = join('.', @arr);

# Here's how we'd turn the calculated $revision into our package $version.
# This makes it difficult to coordinate with other files (debian/changelog
# and rpm/scons.spec) that hard-code the version number, so just go with
# the flow for now.
#pop @arr if $#arr >= 2;
#map {s/^[CD]//, s/^0*(\d\d)$/$1/} @arr;
#$version = join('.', @arr);
$version = '0.04';

$change = $ARG{change};
chomp($change = `$aesub '\$change' 2>/dev/null`) if $aesub && ! $change;

chomp($python_ver = `python -c "import sys; print sys.version[0:3]"`);

chomp($platform = `python -c "from distutils.util import get_platform; print get_platform()"`);

if ($platform eq "win32") {
    $archsuffix = "zip"
} else {
    $archsuffix = "tar.gz"
}

use Cwd;
$test1_dir = File::Spec->catfile(cwd, "build", "test1");
$test2_dir = File::Spec->catfile(cwd, "build", "test2");

$lib_project = File::Spec->catfile("lib", "$project");

# Originally, we were going to package the build engine in a
# private SCons library that contained the version number, so
# we could easily have multiple side-by-side versions of SCons
# installed.  Keep this around in case we ever want to go back
# to that scheme.  Note that this also requires changes to
# runtest.py and src/setup.py.
#$lib_project = File::Spec->catfile("lib", "$project-$version");

$test1_lib_dir = File::Spec->catfile($test1_dir, $lib_project);

$test2_lib_dir = File::Spec->catfile($test2_dir,
                                     "lib",
                                     "python${python_ver}",
                                     "site-packages");

$unpack_dir = File::Spec->catfile(cwd, "build", "unpack");

$env = new cons( ENV => {
			  AEGIS_PROJECT => $ENV{AEGIS_PROJECT},
			  PATH => $ENV{PATH},
			},

                 TEST1_LIB_DIR	=> $test1_lib_dir,
                 TEST2_LIB_DIR	=> $test2_lib_dir,

		 DATE		=> $date,
		 DEVELOPER	=> $developer,
		 REVISION	=> $revision,
		 VERSION	=> $version,

		 SED		=> 'sed',
				   # Use %(-%) around the date so date
				   # changes don't cause rebuilds.
		 SEDFLAGS	=> " %( -e 's+__DATE__+%DATE+' %)" .
				   " -e 's+__DEVELOPER__+%DEVELOPER+'" .
				   " -e 's+__FILE__+%<+'" .
				   " -e 's+__REVISION__+%REVISION+'" .
				   " -e 's+__VERSION__+%VERSION+'",
		 SEDCOM		=> "%SED %SEDFLAGS %< > %>",
		);

#
# Define SCons packages.
#
# In the original, more complicated packaging scheme, we were going
# to have separate packages for:
#
#	python-scons	only the build engine
#	scons-script	only the script
#	scons		the script plus the build engine
#
# We're now only delivering a single "scons" package, but this is still
# "built" as two sub-packages (the build engine and the script), so
# the definitions remain here, even though we're not using them for
# separate packages.
#

$python_scons = {
        'pkg'		=> "python-$project",
        'src_subdir'	=> 'engine',
        'inst_subdir'	=> File::Spec->catfile("lib",
                                               "python1.5",
                                               "site-packages"),
        'prefix'	=> $test2_dir,

        'debian_deps'	=> [ qw(debian/rules debian/control
                                debian/changelog debian/copyright
                                debian/python-scons.postinst
                                debian/python-scons.prerm) ],

        'files'		=> [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
        'filemap'	=> {
                                'LICENSE.txt'	=> '../LICENSE.txt',
        }
};

#
# The original packaging scheme would have have required us to push
# the Python version number into the package name (python1.5-scons,
# python2.0-scons, etc.), which would have required a definition
# like the following.  Leave this here in case we ever decide to do
# this in the future, but note that this would require some modification
# to src/engine/setup.py before it would really work.
#
#$python2_scons = {
#        'pkg'		=> "python2-$project",
#        'src_subdir'	=> 'engine',
#        'inst_subdir'	=> File::Spec->catfile("lib",
#                                               "python2.1",
#                                               "site-packages"),
#        'prefix'	=> $test2_dir,
#
#        'debian_deps'	=> [ qw(debian/rules debian/control
#                                debian/changelog debian/copyright
#                                debian/python2-scons.postinst
#                                debian/python2-scons.prerm) ],
#
#        'files'		=> [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
#        'filemap'	=> {
#                                'LICENSE.txt'	=> '../LICENSE.txt',
#        }
#};

$scons_script = {
        'pkg'		=> "$project-script",
        'src_subdir'	=> 'script',
        'inst_subdir'	=> 'bin',
        'prefix'	=> $test2_dir,

        'debian_deps'	=> [ qw(debian/rules debian/control
                                debian/changelog debian/copyright
                                debian/python-scons.postinst
                                debian/python-scons.prerm) ],

        'files'		=> [ qw(LICENSE.txt README.txt setup.cfg setup.py) ],
        'filemap'		=> {
                                'LICENSE.txt'	=> '../LICENSE.txt',
                                'scons'		=> 'scons.py',
                        }
};

$scons = {
        'pkg'		=> $project,
        'inst_subdir'	=> undef,
        'prefix'	=> $test1_dir,

        'debian_deps'	=> [ qw(debian/rules debian/control
                                debian/changelog debian/copyright
                                debian/scons.postinst
                                debian/scons.prerm) ],

        'files'		=> [ qw(CHANGES.txt LICENSE.txt
				README.txt RELEASE.txt
				os_spawnv_fix.diff scons.1
				script/scons.bat setup.cfg setup.py) ],
        'filemap'		=> {
                                'scons.1'	=> '../doc/man/scons.1',
                        },

        'subpkgs'	=> [ $python_scons, $scons_script ],
        'subinst_dirs'	=> { "python-$project" => $lib_project,
                             "$project-script" => 'bin',
                           },
};

my @src_deps;

for $p ($scons) {
    #
    # Initialize variables with the right directories for this package.
    #
    my $pkg = $p->{'pkg'};

    my $src = 'src';
    $src = File::Spec->catfile($src, $p->{'src_subdir'}) if $p->{'src_subdir'};

    my $build = File::Spec->catfile('build', $pkg);

    my $prefix = $p->{'prefix'};
    my $install = $prefix;
    if ($p->{'inst_subdir'}) {
        $install = File::Spec->catfile($install, $p->{'inst_subdir'});
    }

    #
    # Read up the list of source files from our MANIFEST.in.
    # This list should *not* include LICENSE.txt, MANIFEST,
    # README.txt, or setup.py.  Make a copy of the list for the
    # destination files.
    #
    my @src_files = cons::read_file("$src/MANIFEST.in");
    chomp(@src_files);
    my @dst_files = map(File::Spec->catfile($install, $_), @src_files);

    if ($p->{'subpkgs'}) {
        #
        # This package includes some sub-packages.  Read up their
        # MANIFEST.in files, and add them to our source and destination
        # file lists, modifying them as appropriate to add the
        # specified subdirs.
        #
        foreach $sp (@{$p->{'subpkgs'}}) {
            my $ssubdir = $sp->{'src_subdir'};
            my $isubdir = $p->{'subinst_dirs'}->{$sp->{'pkg'}};
            my $manifest = File::Spec->catfile($src, $ssubdir, 'MANIFEST.in');
            my @f = cons::read_file($manifest);
            chomp(@f);
            push(@src_files, map(File::Spec->catfile($sp->{'src_subdir'}, $_),
                                 @f));
            push(@dst_files, map(File::Spec->catfile($install, $isubdir, $_),
                                 @f));
            my $k;
            foreach $k (keys %{$sp->{'filemap'}}) {
                my $f = $sp->{'filemap'}->{$k};
                next if ! defined $f;
                $k = File::Spec->catfile($sp->{'src_subdir'}, $k); 
                $p->{'filemap'}->{$k} = File::Spec->catfile($sp->{'src_subdir'},
                                                            $f); 
            }
        }
    }

    #
    # Now that we have the "normal" source files, add those files
    # that are standard for each distribution.  Note that we don't
    # add these to dst_files, because they don't get installed.
    # And we still have the MANIFEST to add.
    #
    push(@src_files, @{$p->{'files'}});

    #
    # Now run everything in src_file through the sed command we
    # concocted to expand __FILE__, __VERSION__, etc.
    #
    foreach $b (@src_files) {
        my $s = $p->{'filemap'}->{$b} || $b;
	$env->Command("$build/$b", "$src/$s", "%SEDCOM");
    }

    #
    # NOW, finally, we can create the MANIFEST, which we do
    # by having Perl spit out the contents of the @src_files
    # array we've carefully created.  After we've added
    # MANIFEST itself to the array, of course.
    #
    push(@src_files, "MANIFEST");
    $env->Command("$build/MANIFEST", "$src/MANIFEST.in",
        qq([perl] open(F, ">%>"); print F join("\\n", sort qw(@src_files)), "\\n"; close(F)));

    #
    # Use the Python distutils to generate the packages.
    #
    my $archive = "$build/dist/$pkg-$version.$archsuffix";

    push(@src_deps, $archive);

    my @build_targets = (
	"$build/dist/$pkg-$version.$platform.$archsuffix",
	$archive,
        "$build/dist/$pkg-$version.win32.exe",
    );
    my @install_targets = @build_targets;

    # We can get away with calling setup.py using a directory path
    # like this because we put a preamble in it that will chdir()
    # to the directory in which setup.py exists.
    my @bdist_dirs = ("$build/build/lib", "$build/build/scripts");
    my $commands = qq(rm -rf @bdist_dirs && python $build/setup.py bdist
		      python $build/setup.py sdist
                      python $build/setup.py bdist_wininst);

    if ($rpm) {
	chomp($cwd = `pwd`);
	$topdir = "$cwd/$build/build/bdist.$platform/rpm";

	$BUILDdir = "$topdir/BUILD/$pkg-$version";
	$RPMSdir = "$topdir/RPMS/noarch";
	$SOURCESdir = "$topdir/SOURCES";
	$SPECSdir = "$topdir/SPECS";
	$SRPMSdir = "$topdir/SRPMS";

	$specfile = "$SPECSdir/$pkg-$version-1.spec";
	$sourcefile = "$SOURCESdir/$pkg-$version.$archsuffix";
	$rpm = "$RPMSdir/$pkg-$version-1.noarch.rpm";
	$src_rpm = "$SRPMSdir/$pkg-$version-1.src.rpm";

	# We'd like to use the following here:
	#
	#	$env->InstallAs($specfile, "rpm/$pkg.spec");
	#	$env->InstallAs($sourcefile, $archive);
	#
	# but it looks like InstallAs doesn't propogate the
	# signatures correctly, which means that the RPM file
	# wouldn't always get rebuilt when it should.  Work
	# around it.
	use File::Copy;
	$env->Command($specfile, "rpm/$pkg.spec",
		      "[perl] File::Copy::copy('%<', '%>')");
	$env->Command($sourcefile, $archive,
		      "[perl] File::Copy::copy('%<', '%>')");

	if (! -d $BUILDdir) {
	    $cmd = "mkdir -p $BUILDdir; ";
	}
        my @targets = ( $rpm, $src_rpm );
	$env->Command(\@targets, $specfile,
			"${cmd}rpm --define '_topdir $topdir' -ba %<");
	$env->Depends(\@targets, $sourcefile);

        push(@install_targets, @targets);
    };

    @build_src_files = map("$build/$_", @src_files);

    if ($dh_builddeb && $fakeroot) {
        # Debian builds directly into build/dist, so we don't
        # need to add the .debs to the install_targets.
        my $deb = "build/dist/${pkg}_$version-1_all.deb";
	$env->Command($deb, @build_src_files, qq(
                fakeroot make -f debian/rules VERSION=%VERSION DH_COMPAT=$DH_COMPAT ENVOKED_BY_CONSTRUCT=1 binary-$pkg
                env DH_COMPAT=$DH_COMPAT dh_clean));
        $env->Depends($deb, @{$p->{'debian_deps'}});
    }

    #
    # Now set up creation and installation of the packages.
    #
    $env->Command([@build_targets], @build_src_files, $commands);

    $env->Install("build/dist", @install_targets);

    #
    # Unpack the archive created by the distutils into build/unpack.
    #
    my @unpack_files = map("$unpack_dir/$pkg-$version/$_", @src_files);

    # We'd like to replace the last three lines with the following:
    #
    #	tar zxf %< -C $unpack_dir
    #
    # but that gives heartburn to Cygwin's tar, so work around it
    # with separate zcat-tar-rm commands.
    Command $env [@unpack_files], $archive, qq(
        rm -rf $unpack_dir/$pkg-$version
	zcat %< > .temp
        tar xf .temp -C $unpack_dir
	rm -f .temp
    );

    #
    # Run setup.py in the unpacked subdirectory to "install" everything
    # into our build/test subdirectory.  Auxiliary modules that we need
    # (TestCmd.py, TestSCons.py, unittest.py) will be copied in by
    # etc/Conscript.  The runtest.py script will set PYTHONPATH so that
    # the tests only look under build/test.  This makes sure that our
    # tests pass with what we really packaged, not because of something
    # hanging around in the development directory.
    #
    # We can get away with calling setup.py using a directory path
    # like this because we put a preamble in it that will chdir()
    # to the directory in which setup.py exists.
    Command $env [@dst_files], @unpack_files, qq(
	rm -rf $install
        python $unpack_dir/$pkg-$version/setup.py install --prefix=$prefix
    );
}

#
# Arrange for supporting packages to be installed in the test directories.
#
Export qw( env );

Build "etc/Conscript";

#
# Documentation.
#
Link 'build/doc' => 'doc';

Build 'build/doc/Conscript';


#
# If we're running in the actual Aegis project, pack up a complete
# source archive from the project files and files in the change,
# so we can share it with helpful developers who don't use Aegis.
#
# First, lie and say that we've seen any files removed by this
# change, so they don't get added to the source files list
# that goes into the archive.
#

if ($change) {
    foreach (`aegis -list -unf -c $change cf 2>/dev/null`) {
	$seen{"$1\n"}++ if /^(?:source|test) remove(?:\s.*)+\s(\S+)$/;
    }

    eval '@src_files = grep(! $seen{$_}++,
		    `aegis -list -terse pf 2>/dev/null`,
		    `aegis -list -terse cf 2>/dev/null`)';

    @src_files = grep($_ !~ /(\.aeignore|\.consign)$/, @src_files);

    if (@src_files) {
	chomp(@src_files);

	foreach $file (@src_files) {
	    $env->Command("build/$project-src/$file", 
			  $file, 
	                  qq(%SEDCOM
                             chmod --reference=%< %>)
            );
	}

	$env->Command("build/dist/$project-src-$version.tar.gz",
    		      @src_deps,
		      map("build/$project-src/$_", @src_files),
		      qq(
		rm -rf build/$project-src-$version
		cp -rp build/$project-src build/$project-src-$version
		find build/$project-src-$version -name .consign -exec rm {} \\;
		tar zcf %> -C build $project-src-$version
	));
    }
}