package votorola::b::Build; # Copyright 2008-2010, 2013, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA 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 VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.

use strict; use warnings;
=pod

=head1 DESCRIPTION

Build utilities and targets for Votorola.  See the accompanying 'build' script for the
description of the various targets.

=cut

BEGIN
{
    use Exporter ();
    our @ISA; @ISA = qw( Exporter );
    our @EXPORT_OK; @EXPORT_OK = qw( build_cache_root call_stack_indentation ensure_dir
      %option out_dir );
}
our @EXPORT_OK;

{
    use votorola::b::Config qw( do_fail config_basepath_from_package );
    my $config_path = config_basepath_from_package(__PACKAGE__) . '.pl';
    do $config_path or do_fail $config_path;
}



sub build__target_clean()
{
    use File::Path qw( rmtree );
    print call_stack_indentation() . "clean\n";

    rmtree( build_cache_root() . '/votorola' );
    rmtree( out_dir() );
}



sub build__target_doc()
{
    use File::Basename ();
    use votorola::b::FileSync qw( $from_dir sync_found $to_dir );
    print call_stack_indentation() . "doc\n";

   # Static copy of the source, lightly filtered
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    $from_dir = 'votorola';
    $to_dir = ensure_dir( out_dir() . '/votorola' );
    File::Find::find( {follow_fast=>1, no_chdir=>1, wanted=>sub
    {
        m'/\.wh\.\.wh\.' and return; # skip AUFS whiteout file of my union mount (MCA)
        m'\.jar$' and return; # skip jar, those we need are deployed by build__target_x
        m'^votorola/a/web/context/votorola\.s\.[^/]+In$' and return; # skip placeholder for GWT build
          # Changing?  Change also in votorola::b::GWT::compile
        m'^votorola/a/web/context/.+\(build\)$' and return; # ... for other built files
        m'^votorola/a/web/context/.+\(run\)$' and return; # ... and runtime files

        sync_found();
    }}, $from_dir );

   # CSS optimization
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if( defined $votorola::b::Build::option{'optimizeCSS'} )
    {
        _optimize_css();
    }

   # Static pages and resources for the web-based voter interface
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    $to_dir = ensure_dir( out_dir() . '/votorola/a/web/context' );
    sync_to_dir( "votorola/_/ie-png32-fix.js", $to_dir );
    sync_to_dir( "votorola/_/brand/logo-16.png", $to_dir );

   # Example configuration
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    copy_site_example( out_dir() . '/votorola/s/_/example' );
}



sub build__target_javadoc()
{
    use votorola::b::Java qw( compile javadoc );
    print call_stack_indentation() . "javadoc\n";

    compile();
    javadoc();
}



sub build__target_release()
{
    print call_stack_indentation() . "release\n";

    build__target_doc();
    build__target_x();
    build__target_xgwt();
    build__target_javadoc();
}



sub build__target_x()
{
    use votorola::b::FileSync qw( $from_dir sync_found sync_to_dir sync_to_file $to_dir );
    use votorola::b::Java qw( compile compile_class_outdir );
    print call_stack_indentation() . "x\n";

   # Votorola jar
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    my $out_dir_lib = ensure_dir( out_dir().'/votorola/a/web/context/WEB-INF/lib' );
    my $jar_file = $out_dir_lib . '/votorola.jar';
    compile();

    $from_dir = '.'; # jar portions of source too, for runtime access
    $to_dir = compile_class_outdir();
    File::Find::find( {follow_fast=>1, no_chdir=>1, wanted=>sub
    {
        my $f = $File::Find::name; # ./PATH
        filter:
        {
            $f =~ m'^\./\.hg' and return; # exclude repo internals
            $f =~ m'^\./votorola/b/mvn' and return; # exclude maven symlinks
            $f =~ m'^\.gwt\.xml$' and last; # include Google Web Toolkit modules, per Ant build that GWT ships with

            if( $f =~ m'^\./votorola/(?:.+/)?(?:WC|WP)_[A-Za-z0-9]+\.(.+)$' ) # web markup files
            {
                $1 eq '.java' and return; # exclude the Java code

                last; # include all others (mostly .html)
            }

            $f eq    './votorola/a/locale' and last;
            $f =~ m'^\./votorola/a/locale/[^/]+\.properties$' and last; # include localized resource bundles

            $f eq    './votorola/g/locale' and last;
            $f =~ m'^\./votorola/g/locale/[^/]+\.properties$' and last; # localized "

            return; # exclude all others
        }
        sync_found();
    }}, $from_dir );

    votorola::b::Java::jar( compile_class_outdir(), $jar_file );

   # Runtime library jars
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    for my $jar( _runtime_library_jar_list() )
    {
        my $to_jar = $out_dir_lib . '/' . File::Basename::basename( $jar );
        sync_to_file( $jar, $to_jar );

        my $txt = $jar;
        if( $txt =~ s!\.[^/.]+$!.txt! ) # foo.jar -> foo.txt
        {
            my $to_txt = $out_dir_lib . '/' . File::Basename::basename( $txt );
            if( !-f $txt ) # no foo.txt
            {
                $txt = File::Basename::dirname( $jar ) . '/COPYING'; # for Apache licenced jars outside of revision control (GWT only, at present)
            }
            next if !-f $txt; # no licence at all

            sync_to_file( $txt, $to_txt );
        }
    }

   # Command-line executables
   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    _jar_executable( 'votorola.s.line', 'VOCount' );
    _jar_executable( 'votorola.s.line', 'VODeliver' );
    _jar_executable( 'votorola.s.mail', 'VOFaceMail', 'voface-mail' );
    _jar_executable( 'votorola.s.line', 'VOHarvest' );
    _jar_executable( 'votorola.s.line', 'VOLog' );
    _jar_executable( 'votorola.s.line', 'VOMir' );
    _jar_executable( 'votorola.s.line', 'VONop' );
    _jar_executable( 'votorola.s.line', 'VOQProperties' );
    _jar_executable( 'votorola.s.line', 'VOScript' );
    _jar_executable( 'votorola.s.line', 'VOTer' );
    _jar_executable( 'votorola.s.line', 'VOTrace' );
    _jar_executable( 'org.webharvest',  'CommandLine', 'vowebharvest' );

}



sub build__target_xgwt()
{
    use votorola::b::GWT ();
    build__target_x();
    print call_stack_indentation() . "xgwt\n";

    votorola::b::GWT::compile();
}



=pod

=head1 EXPORTS

=over 4

=cut

# ----------------------------------------------------------------------------------------


=pod

=item B<call_stack_indentation>()

An indentation string reflecting the caller's call stack.

=cut

sub call_stack_indentation()
{
    my $caller_bound = 1; # so far
    while( caller( $caller_bound ))
    {
        ++$caller_bound;
    }
    my $caller_count = $caller_bound - 1;
    --$caller_count; # subtract this call
    ++$caller_count; # correction (unknown reason)
    return '  ' x $caller_count;
}



=pod

=item B<ensure_dir>( $dir ) ret $

Ensures that $dir exists.  Returns $dir.

=cut

sub ensure_dir( $ )
{
    use File::Path qw( mkpath );
    my $dir = shift;
    -d $dir or mkpath $dir or die $!;
    return $dir;
}



=pod

=item %B<option>

A hash of command line options.

=cut

# defined by command line script 'build'



=pod

=back

=head1 PRIVATE

=over 4

=cut

##### P r i v a t e ######################################################################


our $_build_cache_dir;
{
    $_build_cache_dir = ensure_dir( build_cache_root() . '/votorola' );
}



=pod

=item B<_collapsed_path>( $I<path> )

Returns the path without redundant 'foo/../' segments.

=cut

sub _collapsed_path( $ )
{
    my $path = shift;
    while( $path =~ s!([^/.]+)/\.\./!! ) {}
    return $path;
}

sub _collapsed_path_in_quotes( $ ) # fudge of same that does not collapse any quotes
{
    my $path = shift;
    while( $path =~ s!([^/.'"]+)/\.\./!! ) {}
    return $path;
}



=pod

=item B<_inline_css>( $I<inDir>, $I<inDirFromRoot>, $I<inFile>, $I<out> )

Transfers the content of $inFile to $out, recursively inlining any imported files.
$inFile is the short filename alone.  If $inDir is not empty, then it must end with a /.
$inDirFromRoot is $inDir expressed as a relative path from the root file to which all
imports are inlined.  This is empty in the case of the root file itself.

=cut

    our %_inline_css_path; # hash of paths inlined, to prevent duplication

    sub _inline_css( $$$$ ); # avoid warning "called too early to check prototype" on recursive call

sub _inline_css( $$$$ )
{
    my $inDir = shift;
    my $inDirFromRoot = shift;
    my $inFile = shift;
    my $out = shift;

    my $inPath = $inDir . $inFile;
    if( $inDirFromRoot )
    {
        $_inline_css_path{$inPath} and return; # previously inlined
    }
    else
    {
        undef %_inline_css_path; # new root file, nothing previously inlined
    }
  # print( " -- inlining $inPath\n" ); # TEST
    $_inline_css_path{$inPath} = 1;
    my $in;
    open( $in, '<', $inPath ) or die "$! ($inPath)";
    for( ;; )
    {
        my $line = readline( $in );
        if( !$line )
        {
            print( $out "\n" ); # a final newline in case there wasn't one
            last;
        }

        if( $line =~ m'^@import "(.*?)([^/]+)";' )
        {
            _inline_css( _collapsed_path($inDir.$1), _collapsed_path($inDirFromRoot.$1), $2, $out );
        }
        else
        {
            if( $line =~ s!(url\(\s*["']?)!$1$inDirFromRoot! ) # adjust URL, assumed to be relative
            {
                $line = _collapsed_path_in_quotes( $line );
            }
            print( $out $line );
        }
    }
}



=pod

=item B<_jar_executable>( $I<package_name>, $I<class_name>; $I<executable_name> )

Creates the executable for class $package_name.$class_name.  The default $executable_name
is the lower case of $class_name.

=cut

sub _jar_executable( $$ )
{
    use File::Basename ();
    use votorola::b::Console qw( print_score $verbosity );
    use votorola::b::Function qw( system_exit_decode );
    use votorola::b::Java qw( JDK_dir list_found_jar @list_found_jar );

    my $package_name = shift;
    my $class_name = shift;
    my $executable_name = shift; defined $executable_name or $executable_name = lc( $class_name );

    my $exec_jar_file = ensure_dir(out_dir().'/votorola/s/_/bin') . '/' . $executable_name;
    my $manifest_file = $_build_cache_dir . '/executable-jar-manifest';
    open FILE, '>', $manifest_file or die;
    {
        print FILE "Main-Class: $package_name.$class_name\n";
        print FILE 'Class-Path: ../../../a/web/context/WEB-INF/lib/votorola.jar';
        for my $jar( _runtime_library_jar_list() )
        {
            print FILE "\n  ../../../a/web/context/WEB-INF/lib/" . File::Basename::basename( $jar ); # a separate line for each, else the jar tool complains when the line exceeds a few hundred characters or so "java.io.IOException: line too long"
              #           ^ one space to mark the line continuation, another to delimit the items as usual
        }
        print FILE "\n";
        close FILE; # ^ beware "the last line of a manifest file will not be parsed if it doesn't end with a new line character"
    }

    my $command = JDK_dir() . "/bin/jar cmf $manifest_file $exec_jar_file"; # empty but for manifest
    if( $verbosity ) { print "\n$command\n"; print_score( undef, '^' ); }
    system $command; $? != 0 and exit system_exit_decode( $? );

    chmod( 0755, $exec_jar_file ) or warn;
}



=pod

=item B<_optimize_css>()

Optimizes CSS files as defined for build option --optimizeCSS.

=cut

sub _optimize_css()
{
    my $out;
    open( $out, '>', out_dir() . '/votorola/a/web/context/stage/module.css' );
    _inline_css( 'votorola/a/web/context/stage/', '', 'module.css', $out );
}



=pod

=item B<_runtime_library_jar_list>()

The list of all library jars needed at runtime.  The list is cached.

=cut

sub _runtime_library_jar_list()
{
    use votorola::b::GWT ();
    use votorola::b::Java qw( list_found_jar @list_found_jar );

    our @_runtime_library_jar; # cached
    if( !@_runtime_library_jar ) # then lazily create it
    {
        @list_found_jar = ();
        File::Find::find( {follow_fast=>1, no_chdir=>1, wanted=>sub
        {
            my $f = $File::Find::name;
            $f eq 'votorola/g/web/servlet-api.jar' and return; # not needed, servlet container (Tomcat) already has this
            $f =~ m'^votorola/g/web/gwt/[^/]+\.jar$' and return; # only for GWT compile|devmode

            list_found_jar();
        }}, 'votorola' );
        push( @list_found_jar, votorola::b::GWT::runtime_library_jar_list() ); # external to Votorola

        @_runtime_library_jar = @list_found_jar;
    }
    return @_runtime_library_jar;
}

=pod

=back

=cut

1;