#!/usr/bin/perl -w
# $Id: MakeMakerMaker.pl,v 1.5 2004/06/17 17:49:13 gwolf Exp $
######################################
# Comas - Conference Management System
######################################
# Copyright 2003 CONSOL
# Congreso Nacional de Software Libre (http://www.consol.org.mx/)
#   Gunnar Wolf <gwolf@gwolf.cx>
#   Manuel Rabade <mig@mig-29.net>
#
# 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
######################################

######################################
# Script: MakeMakerMaker.pl
# Converts from the directory hierarchy we use for development to a more
# standardized scheme, able for packaging and redistribution.
######################################
# Depends on:
#
# Standalone script, no modules required outside the core Perl distribution.
use strict;
use File::Temp;
use File::Copy;
use File::Find;

=head1 NAME

MakeMakerMaker - Converts from the development directory hierarchy to something
closer to what h2xs/ExtUtils::MakeMaker can handle

=head1 SYNOPSIS

  ./MakeMakerMaker.pl <dest_dir> [<debug_level>]

Creates in dest_dir the following files:

  Comas-w.x.tar.gz
  Comas-HTML-y.z.tar.gz

=head1 DESCRIPTION

MakeMakerMaker will convert between the directory hierarchy we use for our
development work to a standard format to be used by the ExtUtils::MakeMaker
suite.

This script should be removed and the CVS hierarchy moved to something standard
as soon as we can declare a branch :)

The directory hierarchy found in the Comas CVS is structured this way:

  comas             Root directory, general informative files
    /doc            Documentation (DB structure, install instructions...)
    /examples       Examples helping for setting up Comas
       /conf        Main Comas configuration
       /templates   HTML templates
          /consol   HTML Templates used for the CONSOL conference
          /icgtmp   HTML Templates used for the ICGTMP conference
     /perl          Perl modules and scripts live in here
        /Apache     The mod_perl handler
        /Comas      Most of the program's logic - The Perl modules driving the
                    database. 
           /HTML    Comas' front-end, to be used from within mod_perl. Other
                    front-ends should be possible (and desirable), but 
                    currently this is the only one.
          /bin      Some utilitary scripts
             /t     Here we will put the test scripts. Note that we have no 
                    test scripts yet, but they should arrive ;-) Meanwhile,
                    the first valid test is just to get the modules compiled
                    without problems.
       /pkg         Extra files that will be used by the packaging process
       /sql         The database structure, functions and triggers

The MakeMaker structure is not quite rigid, but there are some conventions, I 
am adhering to what I have seen in different Perl modules. The above explained
structure should be converted to the two following Perl module tarballs 
(directory details omitted where same as above):

Comas - The database schema and the Comas backend

  Comas
     /doc           We do some format conversion here - i.e., the DB diagram
                    should be converted to something universally viewable (a
                    PNG?) to be considered as documentation.
     /examples
        /conf
     /lib           All Perl modules go in here
        /Apache
        /Comas
     /scripts       What we had in comas/perl/bin
        /sql        Yes, the SQL can hardly be seen as a script... However, we
                    are packaging Perl modules, and therefore don't have a
                    proper place to store them. The database is crucial for
                    the back-end to be used, so we are including them here.
     /t             The test scripts

Comas::HTML - The HTML frontend

  Comas
    /doc
    /examples
        /templates
           /consol
           /icgtmp
    /lib
      /Comas
        /HTML
    /scripts
    /t

This script can look a bit ugly - I intend it to be a mix of Perl and shell,
surely enough to annoy more than one person. Besides, we do not want this 
script to linger around forever, so it is delibratedly not too smart - many
(if not most) filenames/pathnames are hardwired.

MakeMakerMaker can take an optional debugging level as its second parameter. 
This level ranges from 0 (quiet) to 5 (verbose). If no level is specified, 2 is
used by default.

=head1 REQUIRES

File::Temp (core Perl module)

=head1 EXPORTS

Nothing.

=head1 SEE ALSO

L<ExtUtils::MakeMaker>

=head1 AUTHOR

Gunnar Wolf, gwolf@gwolf.cx
Manuel Rabade, mig@mig-29.net

Comas has been developed for CONSOL, Congreso Nacional de Software Libre,
http://www.consol.org.mx/

=head1 COPYRIGHT

Copyright 2004 Gunnar Wolf and Manuel Rabade

This library is free software, you can redistribute it and/or modify it
under the terms of the GPL version 2 or later.
    
=cut

my ($dest_dir, $debug, $work_dirs, $versions, %files);

if (! @ARGV or @ARGV > 2 or 
    (exists $ARGV[1] and ($ARGV[1] !~ /^\d$/ or $ARGV[1]<0 or $ARGV[1]>5))) {
    print "\nError: $0 called with no arguments\n";
    print "    Correct invocation syntax:\n\n";
    print "    $0 dest_dir [debug_level]\n";
    print "For further instructions, please check 'perldoc $0'\n\n";
    exit 1;
}

$dest_dir = $ARGV[0];
$debug = exists $ARGV[1] ? $ARGV[1] : 2;

$versions = get_versions('perl');
$work_dirs = prepare_dirs($versions);

if (my $error = copy_files($work_dirs)) {
    # An error occured while copying the files - Display it and die
    die "Error creating the files to the working directories:\n$error";
}

if (my $error = create_tarballs($work_dirs, $versions)) {
    # An error occured while creating the tarballs - Display it and die
    die "Error creating the tarballs:\n$error";
}

# Remove the temporary working directory
# As it is a recursive removal, it's easier to call the shell's rm - Just 
# remember the logic is opposite to Perl's (0==success).
debug(3, "Removing temporary working directory");
if (system("rm -rf $work_dirs->{tmp}")) {
   warn "Could not remove temporary directory $work_dirs->{tmp}";
}

debug(1, "Finished - tarballs created in $dest_dir");

exit 0;

sub prepare_dirs {
    # Checks we are called from the right directory, creates our temporary 
    # working directories and populates them with the hierarchy described
    # in the above documentation.
    # Gets as its only argument a hash reference containing the version numbers
    # for both the Comas and the Comas_HTML modules
    # Returns a hash reference with 'Comas' holding the directory name for
    # the main Comas module area, 'Comas_HTML' for Comas::HTML's area,
    # 'src' for the source CVS repository tree and 'tmp' for the base temporary
    # working directory
    my ($versions, %dirs);
    $versions = shift;

    if (! -d $dest_dir) {
	debug(3,"Attempting to create $dest_dir");
	if (! mkdir($dest_dir)) {
	    die "$dest_dir does not exist and cannot be created: $!";
	}
    }

    # Check we are in the real source directory
    for my $dir qw(doc examples perl sql pkg) {
	unless (-d $dir) {
	    print "Directory $dir not found\n";
	    print "$0 should be called from the Comas' CVS root directory";
	    exit 1;
	}
    }

    # Get the present working directory
    chomp($dirs{src} = `pwd`);
    debug(4,"Present working directory: $dirs{src}");

    # Possible errors are handled/reported by mkdtemp, we just exit.
    $dirs{tmp} = mkdtemp("$dest_dir/comas_MMM_XXXXX") or 
	die 'Error creating temporary directory';
    debug(3,"Temporary working directory: $dirs{tmp}");

    $dirs{Comas} = $dirs{tmp} . '/Comas-' . $versions->{Comas};
    $dirs{Comas_HTML} = $dirs{tmp} . '/Comas-HTML-' . 
	$versions->{Comas_HTML};

    mkdir ($dirs{Comas}) or
	die "Cannot create $dirs{Comas} directory";
    mkdir ($dirs{Comas_HTML}) or
	die "Cannot create $dirs{Comas_HTML} directory";
    debug(4,"Created $dirs{Comas} and $dirs{Comas_HTML}");

    # Now, populate the directory trees with the needed directories (as stated
    # above, in the documentation)
    for my $dir qw(doc examples examples/conf lib lib/Apache lib/Comas scripts
		   scripts/sql t) {
	debug(5,"Creating $dirs{Comas}/$dir");
	mkdir("$dirs{Comas}/$dir") or
	    die "Cannot create $dirs{Comas}/$dir directory";
    }

    for my $dir qw(doc examples examples/templates examples/templates/consol
		   examples/templates/icgtmp lib lib/Comas lib/Apache
		   lib/Comas/HTML scripts t) {
	debug(5,"Creating $dirs{Comas_HTML}/$dir");
	mkdir("$dirs{Comas_HTML}/$dir") or
	    die "Cannot create $dirs{Comas}/$dir directory";
    }

    return \%dirs;
}

sub copy_files {
    # Copies the files from the CVS hierarchy to the temporary directories
    # Receives as its only parameter a hash reference with the names for
    # the directories created ('Comas' for the backend, 'Comas_HTML' for the
    # HTML front-end).
    # Returns 0 on success, a string describing the error on failure
    #
    # In this function we use extensively the 'copy' function provided by
    # File::Copy.
    #
    # NOTICE: This function has UGLY HARD-CODEDNESS. Please forgive me.
    my ($dirs, $dia_cmd);
    $dirs = shift;

    # First, work on the back-end hierarchy

    # /doc: Install.txt gets deprecated (or at least requires a major rewrite).
    # We don't include it.
    # comas_db.dia should still be included, as it might be useful. We don't
    # expect everybody to have Dia, so we also include it as a PNG image (if dia
    # can be found in this particular system, of course).
    copy('doc/comas_db.dia', "$dirs->{Comas}/doc/comas_db.dia");
    for my $dir (split /:/, $ENV{PATH}) {
	if (-x "${dir}/dia") {
	    $dia_cmd = "${dir}/dia";
	    last;
	}
    }
    if ($dia_cmd) {
	debug(5, "About to start dia -> png conversion ($dia_cmd)");
	system("$dia_cmd -nt png -s800 $dirs->{Comas}/doc/comas_db.dia >& /dev/null");
	debug(4, 'Database diagram converted to PNG');
    } else {
	debug(0, "NOTE: Dia appears not to be installed. Not converting " .
	      "the database diagram.");
    }

    # /examples/conf: comas.conf
    copy('examples/conf/comas.conf',"$dirs->{Comas}/examples/conf/comas.conf");

    # /lib: Copy Perl module (*.pm) under perl/ except for HTML, Apache and 
    # whatever is found under bin/ and CVS/. We process all the directories 
    # (even when empty) in order to create the needed ones.
    find({no_chdir => 1,
	  wanted => sub {
	      my $orig_file = $_;
	      # Some directories we simply don't care about
	      return if $orig_file =~ /(HTML|CVS|bin|Apache)/;
	      # We work only with directories of files ending in .pm
	      return unless -d $orig_file or $orig_file =~ /\.pm$/;

	      if (-d $orig_file) {
		  my $dest_file = $orig_file;
		  $dest_file =~ s/perl/lib/;
		  mkdir("$dirs->{Comas}/$dest_file");
		  debug(5, "Creating $dirs->{Comas}/$dest_file");

	      } else {
		  my $dest_file = $orig_file;
		  $dest_file =~ s/perl/lib/;
		  copy($orig_file, "$dirs->{Comas}/$dest_file") or
		      die "Error copying $orig_file: $!";
	      }
	  }}, 'perl');

    # /scripts: perl/bin/maintenance becomes scripts/purge_comas_sessions
    copy('perl/bin/maintenance',"$dirs->{Comas}/scripts/purge_comas_sessions");

    # /scripts/sql: schema.sql - The database schema
    # functions.sql - SQL functions, triggers and such
    unless (copy('sql/schema.sql', "$dirs->{Comas}/scripts/sql/") and
	    copy('sql/functions.sql', "$dirs->{Comas}/scripts/sql/")) {
	return "Error copying the database definitions";
	}

    # /t: We only have perl/bin/t/Comas.t
    copy('perl/bin/t/Comas.t', "$dirs->{Comas}/t/") or
	return "Error copying the Comas testing script";

    # Makefile.PL from /pkg, generate MANIFEST
    copy('pkg/Comas_Makefile.PL', "$dirs->{Comas}/Makefile.PL");
    create_manifest($dirs->{Comas});

    # Great, now on to Comas-HTML!

    # /doc: writing_templates.txt explains (although maybe not in the best
    # possible way :-/ ) what to put in each template
    copy('doc/writing_templates.txt', 
	 "$dirs->{Comas_HTML}/doc/writing_templates.txt") or
	     die "Error copying doc/writing_templates.txt: $!";

    # /examples/templates: Everything in our examples/templates, once again,
    # ignoring CVS/
    find ({no_chdir => 1,
	   wanted => sub { 
	       my $file = $_;
               # We don't care about the CVS bookkeeping directory
	       return if $file =~ /CVS/;

	       if (-d $file) {
		   mkdir("$dirs->{Comas_HTML}/$file");
		   debug(5, "Creating $dirs->{Comas_HTML}/$file");
	       } else {
		   copy($file, "$dirs->{Comas_HTML}/$file") or
		       die "Error copying $file: $!";
	       }

	   }}, 'examples/templates');

    # /lib: Copy Perl module (*.pm) under perl/Comas/HTML/, except for the
    # bookkeeping CVS/ directory. We process all the directories (even when
    # empty) in order to create the needed ones. Copy perl/Comas/HTML.pm (it
    # is not inside the HTML directory) and perl/Apache/Comas.pm as
    # well.
    find({no_chdir => 1,
	  wanted => sub {
	      my $orig_file = $_;
	      # Some directories we simply don't care about
	      return if $orig_file =~ /CVS/;
	      # We work only with directories of files ending in .pm
	      return unless -d $orig_file or $orig_file =~ /\.pm$/;

	      if (-d $orig_file) {
		  my $dest_file = $orig_file;
		  $dest_file =~ s/perl/lib/;
		  mkdir("$dirs->{Comas_HTML}/$dest_file");
		  debug(5, "Creating $dirs->{Comas_HTML}/$dest_file");

	      } else {
		  my $dest_file = $orig_file;
		  $dest_file =~ s/perl/lib/;
		  copy($orig_file, "$dirs->{Comas_HTML}/$dest_file") or
		      die "Error copying $orig_file: $!";
	      }
	  }}, 'perl/Comas/HTML');

    unless (copy('perl/Comas/HTML.pm',
		 "$dirs->{Comas_HTML}/lib/Comas/HTML.pm") and
	    copy('perl/Apache/Comas.pm',
		 "$dirs->{Comas_HTML}/lib/Apache/Comas.pm")) {
	return "Error copying the HTML front-end modules: $!"
    }

    # /scripts: Nothing yet. We could include generate_schedule as an example, 
    # although I still see it as dirty, hacked together and too CONSOL-specific


    # /t: We only have perl/bin/t/Comas-HTML.t
    copy('perl/bin/t/Comas-HTML.t', "$dirs->{Comas_HTML}/t/") or
	return "Error copying the Comas-HTML testing script";

    # Makefile.PL from /pkg, generate MANIFEST
    copy('pkg/Comas-HTML_Makefile.PL', "$dirs->{Comas_HTML}/Makefile.PL");
    create_manifest($dirs->{Comas_HTML});

    return 0;
}

sub create_manifest {
    my ($dir);
    $dir = shift;

    if (-f "$dir/MANIFEST") {
	die "$dir/MANIFEST already exists, refusing to clobber it!";
	
    }

    open(MANIFEST,">$dir/MANIFEST") or 
	die "Could not create $dir/MANIFEST: $!";

    find (sub {my $file = $File::Find::name;
	       # Include only files in the Manifest
	       return unless -f $file;
	       # Remove the directory part - the Manifest expects only paths
	       # relative to the module's root
	       substr($file, 0, length($dir)+1, '');

	       print MANIFEST "$file\n" }, $dir);

    return 1;
}

sub create_tarballs {
    # Create the tarballs in the destination directory. 
    # Receives a hash reference pointing to the temporary directories as 
    # its first arguments, another one pointing to the correct version numbers
    # as its second argument.
    # Does not return any useful value (this is, in case of an error, it just
    # dies :) )
    my ($dirs, $versions);
    $dirs = shift;
    $versions = shift;

    # Remember: Backwards logic when dealing with the shell
    if (system("cd $dirs->{tmp}; tar czf $dest_dir/Comas-$versions->{Comas}.tar.gz Comas-$versions->{Comas}")) {
	die "Error preparing $dest_dir/Comas-$versions->{Comas}.tar.gz: $!";
    }
    if (system("cd $dirs->{tmp}; tar czf $dest_dir/Comas-HTML-$versions->{Comas_HTML}.tar.gz Comas-HTML-$versions->{Comas_HTML}")) {
	die "Error preparing $dest_dir/Comas-HTML-$versions->{Comas_HTML}.tar.gz: $!";
    }

}

sub get_versions {
    # Gets the version number for the Comas modules. 
    # Receives as its only parameter the directory name where the Perl modules'
    # root is.
    # Returns a hash reference with the keys 'Comas' and 'Comas::HTML' pointing
    # at the version numbers.
    # Dies if the version numbers cannot be retreived. 
    # **** HARDWIRED ALERT **** 
    # The search for the version number is VERY simplistic. The relevant file 
    # is opened and matched against /^\s*\$VERSION\s*=\s*([\d\.]+)\s*;/ - If 
    # the pattern is not found, this function will just die.
    my ($dirname, $versions);
    $dirname = shift;

    for my $module qw (Comas Comas/HTML) {
	debug(5,"Opening $dirname/$module.pm");
	open(MODULE,'<', "$dirname/$module.pm") or
	    die "Cannot open $dirname/$module.pm: $!";
	while (my $line = <MODULE>) {
	    if ($line =~ /^\s*\$VERSION\s*=\s*([\d\.]+)\s*;/) {
		$versions->{$module} = $1;
		debug(2, "Version for $module: $versions->{$module}");
		close(MODULE);
		last;
	    }
	}

	die "Cannot retreive module version for $module" 
	    unless exists $versions->{$module};
    }

    # Just be consistent with the rest of the script
    $versions->{Comas_HTML} = $versions->{'Comas/HTML'};
    delete $versions->{'Comas/HTML'};

    return $versions;
}

sub debug {
    # Shows debugging information according to the treshold set in the $debug
    # variable (lexical to the whole script)
    my ($level, @msg);
    $level = shift;
    @msg = @_;

    print "{$level} ", join("\n", @msg),"\n" if $level <= $debug;
    return 0;
}

# $Log: MakeMakerMaker.pl,v $
# Revision 1.5  2004/06/17 17:49:13  gwolf
# Cosmetic retouches.
# Now allows for dia to be anywhere in the search path.
#
# Revision 1.4  2004/06/01 00:12:58  gwolf
# Typos fixed
#
# Revision 1.3  2004/05/27 16:40:25  gwolf
# First working version - We now have semi-standard .tar.gz files! :D
#
# Revision 1.2  2004/05/13 22:59:14  gwolf
# Identifies and creates the temporary directories
#
# Revision 1.1  2004/04/27 23:02:13  gwolf
# Started writing the documentation for the MakeMakerMaker, in order to create standard and installable Comas packages
#
