#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
    if $running_under_some_shell;
##   ____ __  __       _        
##  / ___|  \/  | __ _| | _____ 
##  \___ \ |\/| |/ _` | |/ / _ \
##   ___) ||  | | (_| |   <  __/
##  |____/_|  |_|\__,_|_|\_\___|
##                             
##  SMake -- Makefile generator
##
##  SMake is a powerful mechanism to generate standard Makefiles out
##  of skeleton Makefiles which only provide the essential parts.
##  The missing stuff gets automatically filled in by shared include
##  files. A great scheme to create a huge Makefile hierarchy and to
##  keep it consistent for the time of development.  The trick is
##  that it merges the skeleton and the templates in a
##  priority-driven way. The idea is taken from X Consortiums Imake,
##  but the goal here is not inherited system independency, the goal
##  is consistency and power without the need of manually maintaining
##  a Makefile hierarchy. 
##
##  Copyright (C) 1994-1999 Ralf S. Engelschall, <rse@engelschall.com>
##
##  This program is free software; it may be redistributed and/or
##  modified only under the terms of the GNU General Public License,
##  which may be found in the SMake source distribution.  Look at the
##  file COPYING. 
##  
##  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.
## 
##  smake_smkmf.pl -- main procedure for "smkmf" executable
##

require 5.0;

#   include the common parts
#   (these will be substituted by 
#    the real contents via ``unrequire'' later)
##   ____ __  __       _        
##  / ___|  \/  | __ _| | _____ 
##  \___ \ |\/| |/ _` | |/ / _ \
##   ___) ||  | | (_| |   <  __/
##  |____/_|  |_|\__,_|_|\_\___|
##                             
##  SMake -- Makefile generator
##
##  SMake is a powerful mechanism to generate standard Makefiles out
##  of skeleton Makefiles which only provide the essential parts.
##  The missing stuff gets automatically filled in by shared include
##  files. A great scheme to create a huge Makefile hierarchy and to
##  keep it consistent for the time of development.  The trick is
##  that it merges the skeleton and the templates in a
##  priority-driven way. The idea is taken from X Consortiums Imake,
##  but the goal here is not inherited system independency, the goal
##  is consistency and power without the need of manually maintaining
##  a Makefile hierarchy. 
##
##  Copyright (C) 1994-1999 Ralf S. Engelschall, <rse@engelschall.com>
##
##  This program is free software; it may be redistributed and/or
##  modified only under the terms of the GNU General Public License,
##  which may be found in the SMake source distribution.  Look at the
##  file COPYING. 
##  
##  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.
## 
##  smake_misc.pl -- miscellaneous stuff
##


#
#   get dirname and basename of program
#
$prgpath = $0;
if ($prgpath =~ m|^[^/]+$|) {
    $prgpath = ".";
}
else {
    $prgpath =~ s|^(.*)/[^/]+$|\1|;
}
$prgname = $0;
$prgname =~ s|^.*/([^/]+)$|\1|;

#
#   define 4.4BSD exit codes
#
%EX = (
    'OK',     0,
    'USAGE', 64,
    'OSERR', 71,
    'IOERR', 74
);

#
#   common definitions
#
$default_includepath = ".:..:/usr/lib/smake";
$tmpdir = "/tmp";

#   Use this function to convert a time() output to a "SUN (13.03.94)"
#   representation.
#
#   Usage: $str = &givetime(time());
#
@dow = ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );
@moy = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );

sub ctime {
    my ($str);

    local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
        localtime(time());
    $str = sprintf("%s %s %2d %02d:%02d:%02d 19%s%s", 
        $dow[$wday], $moy[$mon], $mday, $hour, $min, $sec, $year, 
        $isdst ? " DST" : "");
    return $str;
}


#EOF#
##   ____ __  __       _        
##  / ___|  \/  | __ _| | _____ 
##  \___ \ |\/| |/ _` | |/ / _ \
##   ___) ||  | | (_| |   <  __/
##  |____/_|  |_|\__,_|_|\_\___|
##                             
##  SMake -- Makefile generator
##
##  SMake is a powerful mechanism to generate standard Makefiles out
##  of skeleton Makefiles which only provide the essential parts.
##  The missing stuff gets automatically filled in by shared include
##  files. A great scheme to create a huge Makefile hierarchy and to
##  keep it consistent for the time of development.  The trick is
##  that it merges the skeleton and the templates in a
##  priority-driven way. The idea is taken from X Consortiums Imake,
##  but the goal here is not inherited system independency, the goal
##  is consistency and power without the need of manually maintaining
##  a Makefile hierarchy. 
##
##  Copyright (C) 1994-1999 Ralf S. Engelschall, <rse@engelschall.com>
##
##  This program is free software; it may be redistributed and/or
##  modified only under the terms of the GNU General Public License,
##  which may be found in the SMake source distribution.  Look at the
##  file COPYING. 
##  
##  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.
## 
##  smake_getopts.pl -- own getopts() function
##


#   this subroutine is derived from getopts.pl with the enhancement
#   of the "+" metacharater at the format string to allow a list to
#   be build by subsequent occurance of the same option.

sub Getopts {
    my ($argumentative) = @_;
    my (@args, $first, $rest);
    my ($errs) = 0;
    local ($[) = 0;

    @args = split( / */, $argumentative );
    while(@ARGV && ($_ = $ARGV[0]) =~ /^-(.)(.*)/) {
        ($first,$rest) = ($1,$2);
        $pos = index($argumentative,$first);
        if($pos >= $[) {
            if($args[$pos+1] eq ':') {
                shift(@ARGV);
                if($rest eq '') {
                    ++$errs unless @ARGV;
                    $rest = shift(@ARGV);
                }
                eval "\$opt_$first = \$rest;";
            }
            elsif ($args[$pos+1] eq '+') {
                shift(@ARGV);
                if($rest eq '') {
                    ++$errs unless @ARGV;
                    $rest = shift(@ARGV);
                }
                eval "push(\@opt_$first, \$rest);";
            }
            else {
                eval "\$opt_$first = 1";
                if($rest eq '') {
                    shift(@ARGV);
                }
                else {
                    $ARGV[0] = "-$rest";
                }
            }
        }
        else {
            print STDERR "Unknown option: $first\n";
            ++$errs;
            if($rest ne '') {
                $ARGV[0] = "-$rest";
            }
            else {
                shift(@ARGV);
            }
        }
    }
    $errs == 0;
}


#EOF#
##   ____ __  __       _        
##  / ___|  \/  | __ _| | _____ 
##  \___ \ |\/| |/ _` | |/ / _ \
##   ___) ||  | | (_| |   <  __/
##  |____/_|  |_|\__,_|_|\_\___|
##                             
##  SMake -- Makefile generator
##
##  SMake is a powerful mechanism to generate standard Makefiles out
##  of skeleton Makefiles which only provide the essential parts.
##  The missing stuff gets automatically filled in by shared include
##  files. A great scheme to create a huge Makefile hierarchy and to
##  keep it consistent for the time of development.  The trick is
##  that it merges the skeleton and the templates in a
##  priority-driven way. The idea is taken from X Consortiums Imake,
##  but the goal here is not inherited system independency, the goal
##  is consistency and power without the need of manually maintaining
##  a Makefile hierarchy. 
##
##  Copyright (C) 1994-1999 Ralf S. Engelschall, <rse@engelschall.com>
##
##  This program is free software; it may be redistributed and/or
##  modified only under the terms of the GNU General Public License,
##  which may be found in the SMake source distribution.  Look at the
##  file COPYING. 
##  
##  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.
## 
##  smake_file.pl -- file I/O stuff
##

package file;


#   Use this function to convert a relative path to a reverse path.
#   i.e.   ./xx/abc -> ../../
#
#   SYNOPSIS: $newfilename = &file'RevPath($filename)
#
sub RevPath {
    my ($path) = @_;
    my ($cwd, $dir);

    if ($path =~ m|^/.*|) {
        $revpath = `pwd`;
    }
    else {
        push(@cwd, split(/\//, &CanonFilename(`pwd`)));
        $revpath = '';
        foreach $name (split(/\//, &CanonFilename($path))) {
            if ($name eq "..") {
                $revpath =  pop(@cwd) . ' ' . $revpath;
            }
            elsif ($name eq ".") {
                $revpath = '.' . ' ' . $revpath;
            }
            else {
                $revpath = '..' . ' ' . $revpath;
            }
        }
        $revpath =~ s|^[ ]*(.*[^ ]+)[ ]*$|\1|;
        $revpath =~ s| +|/|g;
    }
    return ($revpath);
}


#   Use this function to convert a filename to absolute path.
#
#   SYNOPSIS: $newfilename = &file'AbsFilename($filename)
#
sub AbsFilename {
    my ($filename) = @_;
    my ($cwd, $dir);

    if ($filename !~ m|^/.*|) {
        if (-d "$filename") {
            $cwd = `pwd`;
            chdir($filename);
            $filename = `pwd`;
            chdir($cwd);
        }
        else {
            $dir = $filename;
            if ($dir =~ m|^[^/]+$|) {
                $filename = "`pwd`/$filename";
            }
            else {
                $dir =~ s|^(.*)/[^/]+$|\1|;
                $cwd = `pwd`;
                chdir($dir);
                $filename = "`pwd`/$filename";
                chdir($cwd);
            }
        }
    }
    return ($filename);
}


#   Use this function to canonicalize a filename.
#
#   SYNOPSIS: $newfilename = &file'CanonFilename($filename)
#
sub CanonFilename {
    local ($filename) = @_;

    $filename =~ s|\n$||;                      # remove newline terminator

    $filename =~ s|/$||;                       # strip useless slash at end

    $filename =~ s|/\./|/|g;                   # canonicalize /ab/./xy/ -> ab/xy
    $filename =~ s|^\./||;                     # canonicalize ./xy -> xy
    $filename =~ s|/\.[/]*$||g;                # canonicalize /ab/./ -> ab

    $filename =~ s|//|/|g;                     # strip unneeded slahes even
    $filename =~ s|//|/|g;                     # strip unneeded slahes odd

    if ($filename !~ m|/\.\./\.\./|) {
        $filename =~ s|/([^/]+)/\.\./|/|g;     # canonicalize /ab/../xy/ -> /xy/
    }

    if ($filename !~ m|^\.\./\.\./|) {
        $filename =~ s|^([^/]+)/\.\./||g;      # canonicalize ab/../xy/ -> xy/
    }

    if ($filename !~ m|/\.\./\.\.[/]*$|) {
        $filename =~ s|/([^/]+)/\.\.[/]*$||g;  # canonicalize /ab/../xy -> /xy
    }

    return ($filename);
}


#   xfts -- eXtended FileTraverSe 
#   This is a modified version of the dodir() function found
#   in ``The Camel Book'' on page 56/57. This version collects
#   all files in a list instead of printing them directly. 
#   It is also sorts each subtree and has the startup bug fixed.
#   It also does a chdir() to the PWD which was active when called
#   and handles the "//<n>" notation and it canonicalises
#   the initial dir name.
#
#   <path>      only the contents of <path>, no subdir recusion
#   <path>/     only the contents of <path>, no subdir recusion
#   <path>//0   same as <path> or <path>/
#   <path>//<N> only the contents of <path> and the contents of
#               all subdirs <N> subdir-steps deep.
#   <path>//    all under path (recursive)
#
#   This functions uses two optimization tricks:
#   1. dirs without real subdirs are scanned very quick due to
#      comparison with its nlink attribute.
#   2. we traverse via chdir to each subtree relatively to avoid
#      full pathname expansion by the UNIX system.
#
#   SYNOPSIS: @filelist = &file'xfts($dirpath);
#
sub xfts {
    my ($dir, $nlink, $depth) = @_;
    my ($dev, $ino, $mode, $subcount);
    my (@flist, $name);
    my ($pwd);

    #   initialize at startup
    if ($nlink == 0) {
        #   handle //<N> notation
        chop($dir) if ($dir =~ /\n$/);        # remove newline terminator
        if ($dir =~ m|(.*)//(\d*)$|) {
            ($dir, $depth) = ($1, $2 eq '' ? -1 : $2); 
        }
        else {
            $depth = 0;
        }

        #   canonicalise remaining dirname
        $dir =~ s|/$||g;                      # strip useless slash at end
        $dir =~ s|//|/|g;                     # strip unneeded slahes even
        $dir =~ s|//|/|g;                     # strip unneeded slahes odd
        $dir =~ s|/\./|/|g;                   # canonicalize /ab/./xy/ -> ab/xy
        $dir =~ s|/([^/]+)/\.\./|/|g;         # canonicalize /ab/../xy/ -> /xy/

        #   determine current working directory
        #   taken from pwd.pl:initpwd()
        #   it is not needed to set it here for this process,
        #   but if xfts() is called again, a correct $ENV{'PWD'}
        #   will speed up a little bit.
        if ($ENV{'PWD'}) {
            local($dd,$di) = stat('.');
            local($pd,$pi) = stat($ENV{'PWD'});
            if ($di != $pi || $dd != $pd) {
                chop($ENV{'PWD'} = `pwd`);
            }
        }
        else {
            chop($ENV{'PWD'} = `pwd`);
        }

        #   remember the CWD
        $pwd = $ENV{'PWD'};
        #   change to the root-dir which should be examined
        #   and initialize the nlink variable
        chdir($dir) || die "$dir: Can't chdir to this directory\n";
        ($dev, $ino, $mode, $nlink) = stat('.');
    }

    #   read all filenames in sorted order
    opendir(DIRFP, '.') || die("$dir: $!\n");
    local(@filenames) = sort(readdir(DIRFP));
    closedir(DIRFP);

    #   step through the files... 
    @flist = ();
    if ($nlink == 2) {
        #   optimization: a directory with no real subdirs has only
        #   two directories (nlinks == 2): dot and dotdot!
        for (@filenames) {
            next if $_ eq '.';    # skip dot
            next if $_ eq '..';   # skip dotdot
            push(@flist, "$dir/$_");
        }
    }
    else {
        #   there are real subdirs...
        $subcount = $nlink - 2;
        for (@filenames) {
            next if $_ eq '.';    # skip dot
            next if $_ eq '..';   # skip dotdot
            $name = "$dir/$_";
            push(@flist, $name);

            next if $subcount == 0;   # no more subdirs

            ($dev, $ino, $mode, $nlink) = lstat($_);
            next unless -d _;     # use _ to use Perls internal knowledge

            if ($depth > 0 || $depth == -1) {
                #   change to directory and recurse into it
                chdir($_) || die "$_: Can't chdir to this directory\n";
            push(@flist, &xfts($name, $nlink, $depth - 1)) if ($depth >= 0);
            push(@flist, &xfts($name, $nlink, -1))         if ($depth == -1);
                chdir('..');
            }

            --$subcount;
        }
    }

    #    change back to the original CWD which was
    #    active when this function was called by the user
    #    this is not executed if this is a recursive call!
    chdir($pwd) if ($pwd ne '');
    
    #    return the file list
    return(@flist);
}
   
package main;

#EOF#

#
#   print command line usage
#
sub usage {
    print <<'EOT';
Usage: smkmf [options] [directory]
  were options are
    -I includepath   add includepath to list of include directories
    -a               operate on all SMakefiles found in the current subtree
    -q               quiet mode
    -x               debug mode
    -V               display version identification string
    -h               display usage list (this one)
EOT
}

#
#   parse argument line
#
if (&Getopts('aI+qVh') == 0) {
    &usage;
    exit $EX{'USAGE'};
}
if ($opt_h) {
    &usage;
    exit $EX{'OK'};
}
if ($opt_V) {
    print "$SMake_Hello\n";
    exit $EX{'OK'};
}
if ($#ARGV == -1) {
    $makedir = '.';
}
elsif ($#ARGV == 0) {
    $makedir = &file'CanonFilename($ARGV[0]);
}
else {
    &usage;
    exit $EX{'USAGE'};
}
$all   = $opt_a ? 1 : 0;
$quiet = $opt_q ? 1 : 0;
@includepath = ();
if ($#opt_I != -1) {
    push(@includepath, @opt_I);
}
push(@includepath, split(':', $default_includepath));


#
#   determine path to ``smake'' program
#
$smakepath='';
foreach $path (split(/:/, $ENV{'PATH'})) {
    if (-x "$path/smake") {
        $smakepath = "$path/smake";
        last;
    }
}
if ($smakepath eq '' && -x "$prgpath/smake") {
    $smakepath = "$prgpath/smake";
}
if ($smakepath eq '') {
    print "$prgname: program \`\`smake\'\' not found\n";
    exit $EX{'OSERR'};
}
$smakepath = &file'CanonFilename($smakepath);
if ($makedir =~ m|^/.*|) {
    $smakepath = &file'AbsFilename($smakepath);
}
else {
    if ($smakepath !~ m|^/.*|) {
        $smakepath = &file'CanonFilename(&file'RevPath($makedir) . '/' . $smakepath);
	}
}


#
#   canonicalize includepath components according to 
#   the make root dir
#
@includepathnew = ();
foreach $path (@includepath) {
    if ($path =~ m|^/.*|) {
        push(@includepathnew, $path);
    }
    else {
        push(@includepathnew, &file'CanonFilename(&file'RevPath($makedir) . '/' . $path));
    }
}
$includepath = join(':', @includepathnew);

#$definclpath = $smakepath;
#$definclpath =~ s|^(.*)/[^/]+$|\1|;
#$definclpath = $definclpath . "/include";
#$definclpath = &file'CanonFilename($definclpath);
#$includepath = $includepath . ':' . $definclpath;


#
#   recursive function to process a directory
#
sub ProcessDir {
    my ($echodir, $dir, $inclpath, $smakepath, $mode) = @_;
    my (@flist, $path, @inclpathnew);
    my ($filepath, $filename, $smakefile, $inclpathCur, $smakepathCur);

    if ($echodir ne '') {
        $echodir = "$echodir/";
    }

    @flist = sort(&file'xfts($dir));
    @flistfile = ();
    @flistdir = ();
    foreach $filepath (@flist) {
        if (-f "$filepath") {
            push(@flistfile, $filepath);
        }
        else {
            push(@flistdir, $filepath);
        }
    }
    @flist = (sort(@flistfile), sort(@flistdir));

    foreach $filepath (@flist) {
        $filepath = &file'CanonFilename($filepath);
        $filename = $filepath;
        $filename =~ s|^.*/([^/]+)$|\1|;

        if (-f "$filepath" && $filename =~ m|^([sS][mM]akefile)$|) {
            #   process smakefile
            $found = 1;
            print "$smakepath -I$inclpath $filepath\n";
            system("$smakepath -I$inclpath $filepath");
        }

        if (-d "$filepath" && $mode == 1) {

            #   determine individual include path
            @inclpathnew = ();
            foreach $path (split(/:/, $inclpath)) {
                if ($path =~ m|^\/.*|  ||
                    $path =~ m|^\.$|   ||
                    $path =~ m|^\.\.$|   ) {
                    push(@inclpathnew, $path);
                }
                else {
                    push(@inclpathnew, &file'CanonFilename("../$path"));
                }
            }
            $inclpathCur = "@inclpathnew";
            $inclpathCur =~ s| |:|g;

            #   determine individual path to smake binary
            if ($smakepath =~ m|^\/.*|) {
                $smakepathCur = $smakepath;
            }
            else {
                $smakepathCur = "../$smakepath";
            }
            $smakepathCur = &file'CanonFilename($smakepathCur);

            #   process subdir
            if ($quiet == 0) {
                print "===> $echodir$filename\n";
            }
            chdir ($filename);
            &ProcessDir("$echodir$filename", '.', $inclpathCur, $smakepathCur, $mode);
            chdir ("..");
        }
    }
}


#
#   start of actual processing
#
$found = 0;

$ocwd = `pwd`;
chdir($makedir);
if ($quiet == 0) {
    print "[dir=$makedir]\n";
}

&ProcessDir('', '.', $includepath, $smakepath, $all);

chdir($ocwd);

if ($found == 0) {
    print "$prgname: no \`\`smakefile\'\', \`\`Smakefile\'\' or \`\`SMakefile\'\' files found\n";
}

#
#   exit gracefully
#
exit $EX{'OK'};


#EOF#
