#!/usr/bin/python

#!/usr/bin/python

'''\
bake.py  --  Yet another Make replacement

This one can handle header dependencies.
'''

version = '1.0'

copyright = '''\
Copyright (C) 2004 Bertram Scharpf, <software@bertram-scharpf.de>

This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'''

license = '''\
This file is part of the project:
    Make replacement `bake'

%s

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser 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
Or you can find it at: <http://www.gnu.org/copyleft>.

''' % copyright

bugs = '''\
Report bugs to <software@bertram-scharpf.de>.
'''


prgname = __doc__[ :__doc__.index( '.')]



# From Python 2.4 on, this is obsolete.
if not 'sorted' in globals():
    def sorted( l):
        m = l[:]
        m.sort()
        return m

import sys
import os


joinlines = os.linesep.join



class Error (Exception):
    pass


def check():
    if sys.version_info < (2, 3):
        msg = 'Bake needs at least Python 2.3, this is %s!' % sys.version[:3]
        raise AssertionError( msg)

check()



sys.path[ 0:0] = [ '']



class NullFile:
    def write( self, x):
        pass




class Option:
    instances = []

    def lookup( opt):
        for i in Option.instances:
            if opt in i.names:
                return i
        raise Error( 'Unknown option: `%s\'.' % opt)
    lookup = staticmethod( lookup)

    worked = False

    noact   = False
    anyway  = False
    output  = sys.stdout
    verbose = NullFile()
    files   = []

    defaultfiles = [ 'bakefile', 'Bakefile']

    def assurefile():
        if not Option.files:
            for f in Option.defaultfiles:
                if os.path.exists( f):
                    Option.loadfile( f)
                    break
    assurefile = staticmethod( assurefile)

    def loadfile( f):
        gl = globals().copy()
        execfile( f, gl)
        Option.files += [ f]
    loadfile = staticmethod( loadfile)


    def __init__( self, names, param, doc, func):
        if type( names) == type( ''):
            names = (names,)
        self.names, self.param, self.doc, self.func = names, param, doc, func
        Option.instances += [ self]

    def doit( self, restarg, argv):
        if self.param:
            if not restarg:
                restarg = argv.pop( 0)
            if type( self.func) == type( ''):
                exec self.func % { self.param : restarg}
            else:
                self.func( restarg)
            return None
        else:
            if type( self.func) == type( ''):
                exec self.func
            else:
                self.func()
            return restarg

    def docstring( self):
        indent = ' ' * 4
        coldoc = 20
        names = [ indent + [ '-', '--'][ len( n) > 1] + n for n in self.names]
        if self.param:
            names = [ n + ' <%s>' % self.param for n in names]
        return joinlines( names) + ' ' * (coldoc - len( names[ -1])) + self.doc



def showversion():
    print '%s %s' % (prgname.capitalize(), version)
    print '(C) 2004 Bertram Scharpf, <software@bertram-scharpf.de>'
    print
    print '''\
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
'''
    Option.worked = True

def helper():
    print 'Usage: %s [ option | target | assignment ] ...' % prgname
    print
    ok = [ (x.names[ 0].lower(), x.names[ 0].swapcase(), x)
             for x in Option.instances]
    ok.sort()
    for ol, os, o in ok:
        print o.docstring()
    Option.worked = True
    print

def chdir( dir, back = False):
    to = back and 'Back in' or 'Entering'
    dir = os.path.expanduser( dir)
    print >>Option.output, to + ' directory `%s\'.' % dir
    os.chdir( dir)

def setugid( usergrp, back = False):
    import pwd
    import grp
    if type( usergrp) == type( ''):
        s = usergrp.split( ':')
        if len( s) == 2:
            def idorname( i, fn):
                i = i.strip()
                if i.isdigit():
                    return int( i)
                else:
                    return fn( i)[ 2]
            u = idorname( s[ 0], pwd.getpwnam)
            if s[ 1]:
                g = idorname( s[ 1], grp.getgrnam)
            else:
                g = pwd.getpwuid( u).pw_gid
        else:
            u, g = os.lstat( usergrp)[ 4: 6]
    else:
        u, g = usergrp
    to = back and 'Reappearing as' or 'Becoming'
    un, gn = pwd.getpwuid( u)[ 0], grp.getgrgid( g)[ 0]
    print >>Option.output, to + ' %s:%s.' % (un, gn)
    os.setegid( g)
    os.seteuid( u)





class Var:

    '''Var  --  define a variable

    Assign a value to a variable.  Example:

      Var( 'CC', 'gcc-3.3')

    If a variable is not defined, it is looked up in the environment.
    If it isn't given there either, it's assumed to be empty.
    '''

    dict = {}

    envpreceding = False

    def lookup( name):
        if Var.envpreceding:
            return os.environ.get( name, Var.dict.get( name, ''))
        else:
            return Var.dict.get( name, os.environ.get( name, ''))
    lookup = staticmethod( lookup)

    def expand( s, macros = None):
        r = ''
        while True:
            dollar = s.find( '$')
            if dollar < 0:
                r += s
                break
            r += s[ :dollar]
            s = s[ dollar + 1:]
            if s[ 0] == '(':
                t, s = s[1:].split( ')', 1)
            elif s[ 0] == '{':
                t, s = s[1:].split( '}', 1)
            else:
                t, s = s[ :1], s[ 1:]
            t = Slicing( t)
            m = None
            if t.var == '$':
                m = '$'
            elif macros:
                m = macros( t.var)
                if m:
                    m = Var.joiniflist( t.indexof( m))
            if not m:
                m = Var.lookup( t.var)
                if t.sl:
                    m = t.indexof( m.split( os.pathsep), os.pathsep)
                m = Var.expand( m, macros)
            r += m
        return r
    expand = staticmethod( expand)

    def joiniflist( l):
        if type( l) in [ type( []), type( ())]:
            return ' '.join( l)
        else:
            return l
    joiniflist = staticmethod( joiniflist)

    def __init__( self, name, val):
        self.name, self.val = name, val
        Var.dict[ self.name] = self.val
        print >>Option.verbose, 'Variable `%s\' set to %s.' % \
            (self.name, repr( self.val))



class Ext:
    import os

    pathsep, extsep = os.sep, os.extsep

    def split( name):
        ps = name.rfind( Ext.pathsep) + 1
        es = name.rfind( Ext.extsep, ps + 1)  # ".spamrc"'s ext is not "spamrc"
        if es > 0  and  es + 1 < len( name):
            return name[ :ps], name[ ps:es], name[ es + 1:]
        else:
            return name[ :ps], name[ ps:],   ''
    split = staticmethod( split)

    def join( *a):
        if len( a) == 1  and \
                (type( a[ 0]) == type( []) or type( a[ 0]) == type( ())):
            a = a[ 0]
        e = Ext.extstrip(  a[ -1])
        n = Ext.namestrip( a[ -2], e)
        if e:
            n += Ext.extsep + e
        return os.path.normpath( os.path.join( *(list( a[ :-2]) + [ n])))
    join = staticmethod( join)

    def extstrip( ext):
        return ext.lstrip( Ext.extsep)
    extstrip = staticmethod( extstrip)

    def namestrip( name, ext):
        if ext:
            s = name.rstrip( Ext.extsep)
            if s:
                return s
        return name
    namestrip = staticmethod( namestrip)




class Rule:

    '''Rule  --  define a bake rule

    Simply construct an instance in your bakefile like this:

      Rule( File, 'myprg.o', 'myprg.c', 'cc -o $@ $<')
    or
      Rule( cls=File, name='myprg.o', prereqs='myprg.c', cmds='cc -o $@ $<')

    The cls argument can be 'File' or 'Phony' for targets that aren't
    real files.  The prereqs and cmds arguments can be Python functions.
    Their arguments are: 1. the name of the target and 2. the rule's
    macro expansion functions (see below).  See the Cpp class for an
    example.

    If the prereqs argument is a string with prerequisites separated
    with spaces, it is automatically split.  The cmds argument may be
    a list as well.

    The $ macros apply to those in the original make program.
    Additionally, "$&" means _all_ dependencies, not only the newer
    ones.  There are as well macros yielding the number of depedencies
    or newer targets.  The full list is:

        @   target name
        *   target basename (without extension)
        &   dependencies
        #   number of dependencies
        <   newer dependencies
        =   number of newer dependencies

    If a macro expands to a list, or if an environment variable
    consists of a colon-separated list, it can be indexed as in
    Python: "${&[0]}" means the first dependency.  "${PATH[1:]} means
    all elements of the PATH environment variable except the first.  If
    you find a case where it makes sense to index newer dependencies,
    please let me know.
    '''

    instances = []

    def lookup( name):
        return [ i.builder() for i in Rule.instances if i.name == name]
    lookup = staticmethod( lookup)

    def __init__( self, cls, name, prereqs, cmds = None):
        self.cls, self.name, self.prereqs, self.cmds = cls, name, prereqs, cmds
        Rule.instances += [ self]

    def builder( self):
        r = Build( self.cls, self.name, self.prereqs, self.cmds)
        r.basename = Ext.split( r.target.name)[ 1]
        return r


class Suffix:

    '''Suffix  --  define a bake suffix rule

    Suffix rules are defined similarly to normal rules.  An example:

      Suffix( File, '.o', '.c', '${CC} -o $@ -c $<')

    The initial dots ('.c') can be omitted.  See the Rule class for
    further details.
    '''

    instances = []

    def lookup( name):
        pne = Ext.split( name)
        return [ i.builder( os.path.join( *pne[:-1]))
            for i in Suffix.instances if i.ext == pne[ -1]]
    lookup = staticmethod( lookup)

    def __init__( self, cls, ext, prereqs, cmds = None):
        ext = Ext.extstrip( ext)
        if prereqs == '':
            prereqs = Ext.extsep
        self.cls, self.ext, self.prereqs, self.cmds = cls, ext, prereqs, cmds
        Suffix.instances += [ self]

    def builder( self, name):
        ne = Ext.join( name, self.ext)
        r = Build( self.cls, ne, self.prereqs, self.cmds)
        if not callable( r.prereqs):
            r.prereqs = [ x.startswith( '=') and x[1:] or Ext.join( name, x)
                          for x in r.prereqs]
        r.basename = name
        return r




class Target:

    def __cmp__( self, other):
        return cmp( self.mtime, other.mtime)

    def nonexistent( self):
        return self.mtime is None

    def __repr__( self):
        return '<target %s, %r>' % (self.name, self.mtime)


class File (Target):

    def since( name):
        if name  and  os.path.exists( name):
            return os.path.getmtime( name)
    since = staticmethod( since)

    def __init__( self, name):
        self.name = name
        self.settime()

    def settime( self):
        self.mtime = File.since( self.name)


class Phony (Target):

    def __init__( self, name):
        self.name = name
        self.mtime = None

    def settime( self):
        import time
        self.mtime = time.time()



class Build:

    def __init__( self, cls, name, prereqs, cmds):
        self.target = cls( name)
        if type( prereqs) == type( ''):
            prereqs = prereqs.split()
        elif prereqs is None:
            prereqs = []
        self.prereqs = prereqs
        if not cmds:
            cmds = []
        elif type( cmds) != type( []):
            cmds = [ cmds]
        self.cmds = cmds
        print >>Option.verbose, 'Building: %s `%s\'.' % (cls.__name__, name)

    def buildname( target):
        l = Rule.lookup( target)
        if not l:
            l = Suffix.lookup( target)
        if l:
            for b in l:
                b.build()
            l = [ x.target for x in l]
            return max( l)
        else:
            s = File( target)
            if s.mtime is None:
                raise Error( 'Don\'t know how to build `%s\'.' % target)
            return s
    buildname = staticmethod( buildname)

    def finddeps( self):
        if callable( self.prereqs):
            prereqs = self.prereqs( self.target.name, self.macros)
        else:
            prereqs = [ Var.expand( p, self.macros)
                        for p in self.prereqs]
        d = []
        for p in prereqs:
            d.append( Build.buildname( p))
        self.deps = d
        print >>Option.verbose, 'Found dependencies: `%s\'.' % \
                                '\' `'.join( [ x.name for x in d])

    def findnewer( self):
        print >>Option.verbose, 'Checking `%s\'.' % self.target.name
        self.finddeps()
        if self.target.mtime is None:
            n = self.deps
        else:
            n = [ x for x in self.deps if x.mtime > self.target.mtime]
        self.newer = n
        print >>Option.verbose, 'Newer targets: `%s\'.' % \
                                '\' `'.join( [ x.name for x in n])

    def build( self):
        name = self.target.name
        self.findnewer()
        if self.newer or self.target.nonexistent() or Option.anyway:
            print >>Option.verbose, 'Start building `%s\'.' % name
            for c in self.cmds:
                if callable( c):
                    if not Option.noact:
                        c( self.target.name, self.macros)
                    else:
                        print >>Option.verbose, 'Call to %r.' % c
                else:
                    Command( c, self.macros).system()
            print >>Option.verbose, 'Successfully built `%s\'.' % name
            if self.cmds:
                self.target.settime()
            elif callable( self.prereqs):
                self.target.mtime = max( [ x.mtime for x in self.deps])
        elif not self.deps:
            print >>Option.output, '`%s\' has no prerequisites.' % name
        else:
            print >>Option.output, '`%s\' is up to date.' % name

    def macros( self, s):
        if   s == '@':  return self.target.name
        elif s == '*':  return self.basename
        elif s == '&':  return [ x.name for x in self.deps]
        elif s == '#':  return len( self.deps)
        elif s == '<':  return [ x.name for x in self.newer]
        elif s == '=':  return len( self.newer)



class Slicing:

    def intif( x):
        if x:
            return int( x)
    intif = staticmethod( intif)

    import re
    brackets = re.compile( '.*(\[ *([-0-9]*)( *: *([-0-9]*))? *\])')

    def __init__( self, var):
        if var:
            sl = Slicing.brackets.match( var)
        else:
            sl = None
        if sl:
            self.var = var[:sl.start( 1)]
            sl = sl.groups()
            self.sl = Slicing.intif( sl[ 1])
            if sl[ 2]:
                self.sl = (self.sl, Slicing.intif( sl[ 3]))
        else:
            self.sl = None
            self.var = var

    def indexof( self, l, joiner = None):
        if not self.sl is None:
            if type( self.sl) == tuple:
                r = l[ self.sl[ 0]: self.sl[ 1]]
                if joiner:
                    return joiner.join( r)
                else:
                    return r
            else:
                return l[ self.sl]
        else:
            return l




class ExitCode( Error):

    def __init__( self, cmd, code):
        self.cmd, self.code = cmd, code
        Error.__init__( self, 'Job returned with exit code %d.' % code)


class Command:

    '''Command  --  execute Commands, expand $ macros

    Example:

      Command( 'gzip -${SPEED} $@', macroexp).system()

    This is used by bake internally and should be called from
    user-defined command functions.

    The macroexp parameter is a function expanding the $ macros.
    Typically, it should be the one handed over in the command
    and prerequisite spotting hooks.'''

    def __init__( self, cmd, macros = None):
        print >>Option.verbose, 'Unexpanded command: `%s\'.' % cmd
        self.cmd = Var.expand( cmd, macros)

    def system( self):
        'Execute command unless no-act option is set.'
        if not Option.noact:
            self.popen()

    def popen( self):
        'Execute command and return output.'
        print >>Option.output, prgname + '$ ' + self.cmd
        f = os.popen( self.cmd)
        d = f.read()
        Option.output.write( d)
        exitcode = f.close()
        if exitcode:
            raise ExitCode( self, exitcode / 256)
        return d



class Cpp:

    '''Cpp  --  finding C header files using the C preprocessor

    Create an instance x = Cpp( 'incdir1 incdir2') and pass its bound
    member function x.findheaders to the rule or suffix rule as the
    prereq argument.  Example:

      Suffix( File, 'c', Cpp( '${MYLIB}').findheaders)

    Put this in your bakefile and the ""-included headers will be
    found and checked if they were being modified.
    '''

    def __init__( self, incdirs = []):
        if type( incdirs) == type( ''):
            incdirs = incdirs.split()
        self.incdirs = incdirs

    def findheaders( self, name, macros):
        'This function is designed to be passed as the prereq argument.'
        incs = ''.join( ['-I %s ' % i for i in self.incdirs])
        cmd = 'cpp -MM ' + incs + name
        d = Command( cmd, macros).popen()
        d = d.replace( '\\' + os.linesep, '').splitlines()[ 0]
        d = d.split( ':')[ 1].strip().split()[1:]
        return d



class Subbake:

    '''Subbake  --  calling subordinated instances of bake

    This is a convenient replacement for commands like e.g.
    'cd examples ; bake.py all'.  Variables as well as all globally
    defined variables, functions, classes are preserved.  After
    execution this original state is recovered.  Example call:

      def examplessametarget( name, macros):
          Subbake().main( macros, '-Cexample', '$@')
    or
          Subbake().main( macros, *('-C example $@'.split()))

    As long as the Subbake instance exists, the system has forgotten
    all previous rules and suffix rules.  After deletion of the
    object, everything is forgotten that was defined at its lifetime.

    Of course, the "$@" macro could have been written as
    "'%s' % name".'''

    def __init__( self):
        print >>Option.output, 'Entering subordinated bake.'
        self.ruleinst   = Rule.instances
        self.suffixinst = Suffix.instances
        self.optfiles   = Option.files
        self.varenvprec = Var.envpreceding
        self.curdir     = os.getcwd()
        self.usrgrp     = os.geteuid(), os.getegid()
        Rule.instances   = []
        Suffix.instances = []
        Option.files     = []
 
    def __del__( self):
        print >>Option.output, 'Leaving subordinated bake.'
        if self.usrgrp != (os.geteuid(), os.getegid()):
            setugid( self.usrgrp, back = True)
        if self.curdir != os.getcwd():
            chdir( self.curdir, back = True)
        Var.envpreceding = self.varenvprec 
        Option.files     = self.optfiles
        Suffix.instances = self.suffixinst
        Rule.instances   = self.ruleinst

    def main( self, macros, *argv):
        argv = [ Var.expand( str( a), macros) for a in argv]
        subdict = globals().copy()
        exec 'main( [ __file__] + argv)' in subdict, locals()



class ChDir:

    '''ChDir  --  change directory and return when finished

    Use this object to change temporarily to another directory.  When
    it is destroyed, a change back is performed automatically.

        def builddocs( name, macros):
            docdir = ChDir( 'doc')
            # do something in doc
    '''

    def __init__(self, dir, macros = None):
        self.olddir = os.getcwd()
        chdir( Var.expand( dir, macros))

    def __del__( self):
        chdir( self.olddir, back = True)



class Archive:
    def __init__( self, name = None, prefix = True, fakeroot = False):
        self.cwdname = os.path.basename( os.getcwd())
        if not name:
            name = self.cwdname
        self.name = name
        if not prefix or not self.cwdname:
            self.prefix = lambda x: x
        if not fakeroot:
            self.fakeroot = lambda i: i

    def buildname( self, to, ext):
        to = os.path.expanduser( to)
        to = os.path.normpath( os.path.join( '..', to))
        if not os.path.exists( to):
            os.makedirs( to)
        return Ext.join( to, self.name or 'rootdir', ext)

    def prefix( self, name):
        return os.path.normpath( os.path.join( self.cwdname, name))

    def fakeroot( self, i):
        i.uid = i.gid = 0
        i.uname = i.gname = 'root'
        return i


    def totar( self, tar, dir, files):
        for f in files:
            name = os.path.join( dir, f)
            arcname = self.prefix( name)
            print >>Option.verbose, 'Tarring `%s\'' % name
            i = tar.gettarinfo( name, arcname)
            i = self.fakeroot( i)
            if i.isreg():
                tar.addfile( i, open( name, 'rb'))
            else:
                i.size = 0L
                tar.addfile( i)

    def targzall( self, to = ''):
        import tarfile
        tarname = self.buildname( to, '.tar.gz')
        print >>Option.output, 'Building tarfile `%s\'.' % tarname
        t = tarfile.open( tarname, 'w:gz')
        os.path.walk( '.', self.totar, t)
        t.close()


    def tozip( self, zfile, dir, files):
        import zipfile
        for f in files:
            name = os.path.join( dir, f)
            arcname = self.prefix( name)
            print >>Option.verbose, 'Zipping `%s\'' % name
            if os.path.isdir( name):
                zfile.writestr( arcname + os.sep, '')
            else:
                sz = os.stat( name).st_size
                ct = sz < 256 and zipfile.ZIP_STORED or zipfile.ZIP_DEFLATED
                zfile.write( name, arcname, ct)

    def zipall( self, to = ''):
        import zipfile
        zipname = self.buildname( to, '.zip')
        print >>Option.output, 'Building zipfile `%s\'.' % zipname
        z = zipfile.ZipFile( zipname, 'w')
        os.path.walk( '.', self.tozip, z)
        z.close()

    def bothall( self, to = ''):
        self.targzall( to)
        self.zipall( to)




class Installer:

    scripts = tuple( [ tuple( [ when + what for when in ['pre', 'post']])
                                                for what in ('inst', 'rm')])

    def _execscript( self, p, macros):
        fn = getattr( self, p, None)
        if fn:
            Command( fn(), macros).system()


    def _getfiles( self, macros):
        both = self.execfiles().copy(), self.conffiles().copy()
        for d in both:
            for k, v in d.items():
                if not type( v) in (type( []), type( ())):
                    v = [ v]
                d[ k] = [ Var.expand( x, macros) for x in v]
        return both

    def _getfilesdirs( self, macros):
        r = []
        for d in self._getfiles( macros):
            e = {}
            for k, v in d.items():
                dir = Var.expand( k, macros)
                if not os.path.isabs( dir):
                    dir = os.sep + dir
                e [ dir] = v
            r += [ e]
        return tuple( r)

    def _sysowner( id):
        if id < 500:
            return id
        return 0
    _sysowner = staticmethod( _sysowner)

    def _expanddicts( self, macros):
        r = []
        for d in self._getfilesdirs( macros):
            l = []
            for dir, v in d.items():
                for src in v:
                    dst = os.path.join( dir, os.path.basename( src))
                    s = os.lstat( src)
                    own = tuple( [ Installer._sysowner( x)
                                   for x in [ s.st_uid, s.st_gid]])
                    l += [ (dst, s.st_mode, own, src)]
            l.sort()
            r += [ l]
        return tuple( r)

    def prereqs( self, name, macros):
        l = []
        ec = self._getfiles( macros)
        for d in ec:
            for k, v in d.items():
                if not type( v) in [ type( []), type( [])]:
                    v = [ v]
                l += v
        return l

    def check( self, name, macros):
        e, c = self._expanddicts( macros)
        found, missed = None, None
        for dst, mode, own, src in e:
            print >>Option.verbose, 'Looking up `%s\'.' % dst
            if os.path.exists( dst):
                found = dst
                odd = missed
            else:
                missed = dst
                odd = found
            if odd:
                msg = 'Installation is not comlete.  '
                msg += 'Last file found: `%s\'.  ' % found
                msg += 'Missing file: `%s\'.  '   % missed
                msg += '(This may be a conflict with another project.)'
                raise Error( msg)
        if found:
            print >>Option.output, 'Project is fully installed.'
            return True
        else:
            print >>Option.output, 'Project is not installed yet.'

    def _assuredir( file):
        path, name = os.path.split( file)
        if not os.path.exists( path):
            Installer._assuredir( path)
            os.mkdir( path)
    _assuredir = staticmethod( _assuredir)

    def _instobject( dst, mode, own, src):
        print >>Option.verbose, 'Installing `%s\'.' % dst
        Installer._assuredir( dst)
        import stat
        if stat.S_ISDIR( mode):
            os.mkdir( dst, mode)
        elif stat.S_ISREG( mode):
            import shutil
            shutil.copy( src, dst)
        elif stat.S_ISLNK( mode):
            os.symlink( os.readlink( src), dst)
        elif stat.S_ISCHR( mode) or stat.S_ISBLK( mode):
            os.mknod( dst, mode, os.lstat( src).st_rdev)
        elif stat.S_ISFIFO( mode):
            os.mkfifo( dst, mode)
        else:
            raise Error( 'File mode not supported: %o, `%s\'.' %
                                                   (mode, dst))
        os.chmod( dst, mode)
        os.chown( dst, *own)
    _instobject = staticmethod( _instobject)

    def install( self, name, macros):
        (preinst, postinst), dummy = Installer.scripts
        self._execscript( preinst, macros)
        e, c = self._expanddicts( macros)
        for dst, mode, own, src in e:
            Installer._instobject( dst, mode, own, src)
        for dst, mode, own, src in c:
            new = dst
            while os.path.exists( new):
                new = Ext.join( dst, 'bake-new')
            if new != dst:
                print >>Option.output, 'Config file exists: `%s\'; ' \
                                       'Installing `%s\'.' % (dst, new)
            Installer._instobject( new, mode, own, src)
        self._execscript( postinst, macros)

    def _rmobject( dst, mode):
        print >>Option.verbose, 'Removing `%s\'.' % dst
        import stat
        if os.path.exists( dst):
            if stat.S_ISDIR( mode):
                os.rmdir( dst)
            else:
                os.remove( dst)
    _rmobject = staticmethod( _rmobject)

    def remove( self, name, macros):
        dummy, (prerm, postrm) = Installer.scripts
        self._execscript( prerm, macros)
        e, c = self._expanddicts( macros)
        e.reverse()
        for dst, mode, own, src in e:
            Installer._rmobject( dst, mode)
        self._execscript( postrm, macros)

    def purge( self, name, macros):
        dummy, (prerm, postrm) = Installer.scripts
        self._execscript( prerm, macros)
        for d in self._expanddicts( macros):
            d.reverse()
            for dst, mode, own, src in d:
                Installer._rmobject( dst, mode)
        self._execscript( postrm, macros)

    def package( self, name, macros):
        os.mkdir( name)
        def inpackdir( file):
            return os.path.join( name, file)

        execfiles, conffiles = self._expanddicts( macros)
        files = execfiles + conffiles
        del execfiles
        def mktar():
            import tarfile
            import pwd, grp
            t = tarfile.open( inpackdir( 'data.tar.gz'), 'w:gz')
            print >>Option.verbose, 'Building tarfile `%s\'.' % t.name
            for dst, mode, own, src in files:
                print >>Option.verbose, 'Tarring `%s\'.' % dst
                i = t.gettarinfo( src, dst)
                i.mode = mode
                u, g = i.uid, i.gid = own
                i.uname, i.gname = pwd.getpwuid( u)[0], grp.getgrgid( g)[0]
                if i.isreg():
                    t.addfile( i, open( src))
                else:
                    t.addfile( i)
            t.close()
        mktar()

        def mkconffiles():
            f = open( inpackdir( 'conffiles'), 'w')
            print >>Option.verbose, 'Building `%s\'.' % f.name
            for dst, mode, own, src in conffiles:
                print >>f, dst
            f.close()
        if conffiles:
            mkconffiles()
            
        def mkmd5():
            m = open( inpackdir( 'md5sums'), 'w')
            print >>Option.verbose, 'Building `%s\'.' % m.name
            import md5
            import stat
            for dst, mode, own, src in files:
                if not stat.S_ISREG( mode):
                    continue
                n = md5.new()
                f = open( src)
                while 1:
                    r = f.read()
                    if not r:
                        break
                    n.update( r)
                print >>m, n.hexdigest(), '', dst[1:]
            m.close()
        mkmd5()

        def mkscripts():
            for ir in Installer.scripts:
                for s in ir:
                    fn = getattr( self, s, None)
                    if fn:
                        f = open( inpackdir( s), 'w')
                        print >>Option.verbose, 'Building `%s\'.' % f.name
                        print >>f, Command( fn(), macros).cmd
                        f.close()
        mkscripts()

    def stdrules( self):
        return (
            Rule( Phony, 'check',   self.prereqs, self.check),
            Rule( Phony, 'install', self.prereqs, self.install),
            Rule( Phony, 'remove',  self.prereqs, self.remove),
            Rule( Phony, 'purge',   self.prereqs, self.purge),
            Rule( Phony, 'package', self.prereqs, self.package))



def printout():
    Option.assurefile()
    indent = ' '* 4
    print 'Variables:'
    for k in sorted( Var.dict.keys()):
        print indent + '%s=%s' % (k, Var.dict[ k])
    print
    print 'Rules:'
    for k in Rule.instances:
        print indent + '%s (%s)' % (k.name, k.cls.__name__)
        print indent * 2 + repr( k.prereqs)
        print indent * 2 + repr( k.cmds)
    print
    print 'Suffix Rules:'
    for k in Suffix.instances:
        print indent + '%s (%s)' % (k.ext, k.cls.__name__)
        print indent * 2 + repr( k.prereqs)
        print indent * 2 + repr( k.cmds)
    print
    Option.worked = True




Option( ('h', 'help'),    None, 'print this message',        helper)
Option( ('V', 'version'), None, 'print version information', showversion)

Option( ('n', 'noexec'),  None, 'do not execute commands',
    'Option.noact = True')

Option( ('v', 'verbose'), None, 'verbose',
    'Option.verbose = Option.output = sys.stdout')

Option( ('s', 'silent'),  None, 'no output (silent)',
    'Option.verbose = Option.output = NullFile()')


Option( ('a', 'anyway'),   None, 'build targets anyway',
    'Option.anyway = True')

Option( ('A', 'noanyway'), None, 'build targets only if neccessary (default)',
    'Option.anyway = False')


Option( ('f', 'file'),    'FILE', 'use FILE as bakefile',
    'Option.loadfile( \'%(FILE)s\')')

Option( ('C', 'chdir'),   'DIR',  'change to directory DIR', chdir)

Option( ('U', 'usergrp'), 'SPEC', 'become user/group SPEC (user:grp or path)',
                                                             setugid)

Option( ('e', 'envprec'),   None, 'environment variables precede bakefiles',
    'Var.envpreceding = True')

Option( ('E', 'noenvprec'), None, '"bakefile variables precede environment',
    'Var.envpreceding = False')


Option( ('p', 'printout'), None, 'print out variables and rules', printout)



def main( argv):
    argv.pop( 0)
    while argv:
        a = argv.pop( 0)
        if len( a) > 1  and  a[ 0] == '-':
            if a[ 1] == '-':
                Option.lookup( a[ 2:]).doit( None, argv)
            else:
                a = a[ 1:]
                while a:
                    a = Option.lookup( a[ :1]).doit( a[ 1:], argv)
        elif '=' in a:
            Option.assurefile()
            k, v = [ x.strip() for x in a.split( '=', 1)]
            if v[ 0] in '"\'':
                v = eval( v)
            Var( k, v)
        else:
            Option.assurefile()
            Build.buildname( a)
            Option.worked = True
    if not Option.worked:
        Option.assurefile()
        if Rule.instances:
            Rule.instances[ 0].builder().build()
        else:
            msg = 'No rules specified.  Try "%s -h" for options.' % prgname
            raise Error( msg)


if __name__ == '__main__':
    main( sys.argv[:])

