#!/usr/bin/env python

# acprep, version 3.0
#
# This script configures my ledger source tree on my Mac OS/X machine.  This
# is not necessary, however, since I keep all the files necessary for building
# checked in to the source tree.  Users can just type './configure && make'.
# This script simply sets up the compiler and linker flags for all the various
# build permutations I use for testing and profiling.

import inspect
import logging
import logging.handlers
import optparse
import os
import re
import shutil
import string
import sys
import time
import tempfile
import datetime

try:
    import hashlib
except:
    import md5

from os.path import *
from stat import *
from subprocess import Popen, PIPE, call

LEVELS = {'DEBUG':    logging.DEBUG,
          'INFO':     logging.INFO,
          'WARNING':  logging.WARNING,
          'ERROR':    logging.ERROR,
          'CRITICAL': logging.CRITICAL}


class CommandLineApp(object):
    "Base class for building command line applications."

    force_exit  = True           # If true, always ends run() with sys.exit()
    log_handler = None

    options = {
        'debug':    False,
        'verbose':  False,
        'logfile':  False,
        'loglevel': False
    }

    def __init__(self):
        "Initialize CommandLineApp."
        # Create the logger
        self.log = logging.getLogger(os.path.basename(sys.argv[0]))
        ch = logging.StreamHandler()
        formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
        ch.setFormatter(formatter)
        self.log.addHandler(ch)
        self.log_handler = ch

        # Setup the options parser
        usage = 'usage: %prog [OPTIONS...] [ARGS...]'
        op = self.option_parser = optparse.OptionParser(usage = usage,
                                                        conflict_handler = 'resolve')
        op.add_option('', '--debug',
                      action='store_true', dest='debug',
                      default=False, help='show debug messages and pass exceptions')
        op.add_option('-v', '--verbose',
                      action='store_true', dest='verbose',
                      default=False, help='show informational messages')
        op.add_option('-q', '--quiet',
                      action='store_true', dest='quiet',
                      default=False, help='do not show log messages on console')
        op.add_option('', '--log', metavar='FILE',
                      type='string', action='store', dest='logfile',
                      default=False, help='append logging data to FILE')
        op.add_option('', '--loglevel', metavar='LEVEL',
                      type='string', action='store', dest='loglevel',
                      default=False, help='set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL')
        return

    def main(self, *args):
        """Main body of your application.

        This is the main portion of the app, and is run after all of the
        arguments are processed.  Override this method to implment the primary
        processing section of your application."""
        pass

    def handleInterrupt(self):
        """Called when the program is interrupted via Control-C or SIGINT.
        Returns exit code."""
        self.log.error('Canceled by user.')
        return 1

    def handleMainException(self):
        "Invoked when there is an error in the main() method."
        if not self.options.debug:
            self.log.exception('Caught exception')
        return 1

    ## INTERNALS (Subclasses should not need to override these methods)

    def run(self):
        """Entry point.

        Process options and execute callback functions as needed.  This method
        should not need to be overridden, if the main() method is defined."""
        # Process the options supported and given
        self.options, main_args = self.option_parser.parse_args()

        if self.options.logfile:
            fh = logging.handlers.RotatingFileHandler(self.options.logfile,
                                                      maxBytes = (1024 * 1024),
                                                      backupCount = 5)
            formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
            fh.setFormatter(formatter)
            self.log.addHandler(fh)

        if self.options.quiet:
            self.log.removeHandler(self.log_handler)
            ch = logging.handlers.SysLogHandler()
            formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
            ch.setFormatter(formatter)
            self.log.addHandler(ch)
            self.log_handler = ch

        if self.options.loglevel:
            self.log.setLevel(LEVELS[self.options.loglevel])
        elif self.options.debug:
            self.log.setLevel(logging.DEBUG)
        elif self.options.verbose:
            self.log.setLevel(logging.INFO)
        
        exit_code = 0
        try:
            # We could just call main() and catch a TypeError, but that would
            # not let us differentiate between application errors and a case
            # where the user has not passed us enough arguments.  So, we check
            # the argument count ourself.
            argspec = inspect.getargspec(self.main)
            expected_arg_count = len(argspec[0]) - 1

            if len(main_args) >= expected_arg_count:
                exit_code = self.main(*main_args)
            else:
                self.log.debug('Incorrect argument count (expected %d, got %d)' %
                               (expected_arg_count, len(main_args)))
                self.option_parser.print_help()
                exit_code = 1

        except KeyboardInterrupt:
            exit_code = self.handleInterrupt()

        except SystemExit, msg:
            exit_code = msg.args[0]

        except Exception:
            exit_code = self.handleMainException()
            if self.options.debug:
                raise
            
        if self.force_exit:
            sys.exit(exit_code)
        return exit_code


class PrepareBuild(CommandLineApp):
    #########################################################################
    # Initialization routines                                               #
    #########################################################################

    def initialize(self):
        self.log.debug('Initializing all state variables')

        self.should_clean      = False
        self.configured        = False
        self.current_ver       = None
        #self.current_flavor    = 'default'
        self.current_flavor    = 'debug'
        self.products_dir      = None
        self.build_dir         = self.source_dir
        self.configure_args    = ['--with-included-gettext']
        self.sys_include_dirs  = []
        self.sys_library_dirs  = []

        self.CPPFLAGS          = []
        self.CCFLAGS           = []
        self.ARCHFLAGS         = []
        self.CXXFLAGS          = []
        self.LDFLAGS           = []
        self.LDARCHFLAGS       = []

        self.envvars = {
            'PYTHON_HOME':     '/usr',
            'PYTHON_VERSION':  '2.6',
            'BOOST_SUFFIX':    None,
            'BOOST_HOME':      '/usr',
            'LEDGER_PRODUCTS': None,
            'CC':              'gcc',
            'CPPFLAGS':        '',
            'CCFLAGS':         '',
            'CXX':             'g++',
            'CXXFLAGS':        '',
            'ARCHFLAGS':       '',
            'LD':              'g++',
            'LDFLAGS':         '',
            'LDARCHFLAGS':     '',
        }

        for varname in self.envvars.keys():
            if os.environ.has_key(varname):
                self.envvars[varname] = os.environ[varname]

                if varname.endswith('FLAGS'):
                    self.__dict__[varname] = string.split(os.environ[varname])
                    self.envvars[varname]  = ''

        # If ~/Products/ or build/ exists, use them instead of the source tree
        # for building
        products = self.default_products_directory()
        if (exists(products) and isdir(products)) or \
           (exists('build') and isdir('build')):
            self.build_dir = None

    def __init__(self):
        CommandLineApp.__init__(self)
        self.log.setLevel(logging.INFO)

        op = self.option_parser
        op.add_option('-j', '--jobs', metavar='N',
                      type='int', action='store', dest='jobs',
                      default=1, help='Allow N make jobs at once')
        op.add_option('', '--boost', metavar='SUFFIX',
                      action="callback", type="string",
                      callback=self.option_boost,
                      help='Set Boost library suffix (ex: "--boost=-mt")')
        op.add_option('', '--force', action="callback",
                      callback=self.option_force,
                      help="Perform every action, without checking")
        op.add_option('', '--glibcxx-debug', action='store_true',
                      dest='use_glibcxx_debug', default=False,
                      help='Define _GLIBCXX_DEBUG=1 during compilation')
        op.add_option('', '--help', action="callback",
                      callback=self.option_help,
                      help='Show this help text')
        op.add_option('', '--local', action="callback",
                      callback=self.option_local,
                      help='Build directly within the source tree (default)')
        op.add_option('', '--no-pch', action="callback",
                      callback=self.option_no_pch,
                      help='Do not use pre-compiled headers')
        op.add_option('', '--no-patch', action='store_true', dest='no_patch',
                      default=False,
                      help='Do not patch the Makefile for prettier output')
        op.add_option('', '--no-git', action='store_true', dest='no_git',
                      default=False,
                      help='Do not call out to Git; useful for offline builds')
        op.add_option('', '--output', metavar='DIR', action="callback",
                      callback=self.option_output,
                      help='Build in the specified directory')
        op.add_option('', '--pch', action="callback",
                      callback=self.option_pch,
                      help='Enable use of pre-compiled headers')
        op.add_option('', '--pic', action="callback",
                      callback=self.option_pic,
                      help='Compile with explicit PIC support')
        op.add_option('', '--products', metavar='DIR', action="callback",
                      callback=self.option_products,
                      help='Collect all build products in this directory')
        op.add_option('', '--trees', action="callback",
                      callback=self.option_trees,
                      help='Use separate build trees for each flavor')
        op.add_option('', '--release', action="callback",
                      callback=self.option_release,
                      help='Setup for doing a faster, once-only build')
        op.add_option('', '--warn', action="callback",
                      callback=self.option_warn,
                      help='Enable full warning flags')

        self.force      = False
        self.no_pch     = False
        self.source_dir = os.getcwd()

        self.initialize()

    def main(self, *args):
        if args and args[0] in ['default', 'debug', 'opt', 'gcov', 'gprof']:
            self.current_flavor = args[0]
            args = args[1:]

        if args:
            cmd = args[0]
            if not PrepareBuild.__dict__.has_key('phase_' + cmd):
                cmd = 'config'
            else:
                args = args[1:]
        else:
            cmd = 'config'

        self.log.info('Invoking primary phase: ' + cmd)
        PrepareBuild.__dict__['phase_' + cmd](self, *args)

    #########################################################################
    # General utility code                                                  #
    #########################################################################

    def execute(self, *args):
        try:
            self.log.debug('Executing command: ' + string.join(args, ' '))

            retcode = call(args, shell=False)
            if retcode < 0:
                self.log.error("Child was terminated by signal", -retcode)
                sys.exit(1)
            elif retcode != 0:
                self.log.error("Execution failed: " + string.join(args, ' '))
                sys.exit(1)
        except OSError, e:
            self.log.error("Execution failed:", e)
            sys.exit(1)

    def get_stdout(self, *args):
        try:
            self.log.debug('Executing command: ' + string.join(args, ' '))

            proc    = Popen(args, shell=False, stdout=PIPE)
            stdout  = proc.stdout.read()
            retcode = proc.wait()
            if retcode < 0:
                self.log.error("Child was terminated by signal",
                               -retcode)
                sys.exit(1)
            elif retcode != 0:
                self.log.error("Execution failed: " + string.join(args, ' '))
                sys.exit(1)
            return stdout[:-1]
        except OSError, e:
            self.log.error("Execution failed:", e)
            sys.exit(1)

    def isnewer(self, file1, file2):
        "Check if file1 is newer than file2."
        if not exists(file2):
            return True
        return os.stat(file1)[ST_MTIME] > os.stat(file2)[ST_MTIME]

    #########################################################################
    # Determine information about the surroundings                          #
    #########################################################################

    def default_products_directory(self):
        if self.envvars['LEDGER_PRODUCTS']:
            return self.envvars['LEDGER_PRODUCTS']
        else:
            return join(os.environ['HOME'], "Products")

    def products_directory(self):
        if not self.products_dir:
            products = self.default_products_directory()

            if not exists(products) or not isdir(products):
                products = join(self.source_dir, 'build')

            products = join(products, basename(self.source_dir))

            self.products_dir = products

        return self.products_dir

    def build_directory(self):
        if not self.build_dir:
            self.build_dir = join(self.products_directory(),
                                  self.current_flavor)
        return self.build_dir

    def ensure(self, dirname):
        if not exists(dirname):
            self.log.info('Making directory: ' + dirname)
            os.makedirs(dirname)
        elif not isdir(dirname):
            self.log.error('Directory is not a directory: ' + dirname)
            sys.exit(1)
        return dirname

    def git_working_tree(self):
        return exists('.git') and isdir('.git') and not self.options.no_git

    def current_version(self):
        if not self.current_ver:
            if self.git_working_tree():
                #date = self.get_stdout('git', 'log', '--format=%ci', '-1', 'HEAD')
                #date = re.sub(" [-+][0-9][0-9][0-9][0-9]$", "", date)
                #when = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
                #self.current_ver = when.strftime("%Y%m%d_%H%M%S")
                #commit = self.get_stdout('git', 'log', '--format=%h', 'HEAD^..HEAD')
                #self.current_ver += "_" + commit
                tag = self.get_stdout('git', 'describe', '--all', '--long')
                self.current_ver = re.sub('heads/', '', tag)
            else:
                self.current_ver = "no-git"
        return self.current_ver

    def need_to_prepare_autotools(self):
        if self.force:
            return 'because it was forced'
        elif self.isnewer('acprep', 'configure'):
            self.should_clean = True
            return 'because acprep is newer than configure'
        elif self.isnewer('acprep', 'Makefile.in'):
            self.should_clean = True
            return 'because acprep is newer than Makefile.in'
        elif self.isnewer('configure.ac', 'configure'):
            return 'because confgure.ac is newer than configure'
        elif self.isnewer('Makefile.am', 'Makefile.in'):
            return 'because Makefile.am is newer than Makefile.in'
        return False

    def phase_products(self, *args):
        self.log.info('Executing phase: products')
        print self.products_directory()

    def phase_info(self, *args):
        self.log.info('Executing phase: info')

        environ, conf_args = self.configure_environment()

        self.log.info("Current version          => " + self.current_version())
        self.log.info("Current flavor           => " + self.current_flavor)
        self.log.info("Source directory         => " + self.source_dir)
        self.log.info("Need to run autogen.sh   => " +
                      str(self.need_to_prepare_autotools()))
        self.log.info("Products directory       => " + self.products_directory())
        self.log.info("Build directory          => " + self.build_directory())
        self.log.info("Need to run configure    => " +
                      str(self.need_to_run_configure()))
        self.log.info("Use _GLIBCXX_DEBUG       => " +
                      str(self.options.use_glibcxx_debug))
        self.log.info("Use pre-compiled headers => " +
                      str('--enable-pch' in conf_args))

        self.log.debug('Configure environment =>')

        keys = environ.keys()
        keys.sort()
        for key in keys:
            if key in ['PATH', 'CC', 'LD', 'CXX'] or \
               key.endswith('FLAGS'):
                self.log.debug('  %s=%s' % (key, environ[key]))

        self.log.debug('Configure arguments   =>')

        for arg in conf_args + list(args):
            self.log.debug('  %s' % arg)

    def phase_sloc(self, *args):
        self.log.info('Executing phase: sloc')
	self.execute('sloccount', 'src', 'python', 'lisp', 'test')

    #########################################################################
    # Configure source tree using autogen                                   #
    #########################################################################

    def phase_gettext(self, *args):
        self.log.info('Executing phase: gettext')

        # configure the template files
        assert exists('po') and isdir('po')
        if not exists(join('po', 'Makevars')):
            assert exists(join('po', 'Makevars.template'))
            self.log.info('Moving po/Makevars.template -> po/Makevars')
            os.rename(join('po', 'Makevars.template'),
                      join('po', 'Makevars'))

        POTFILES_in = open('po/POTFILES.in', 'w')
        for filename in (f for f in os.listdir(join(self.source_dir, 'src'))
                             if re.search('\.(cc|h)', f)):
            POTFILES_in.write(join('src', filename))
            POTFILES_in.write('\n')
        POTFILES_in.close()

    def phase_version(self, *args):
        self.log.info('Executing phase: version')
        version_m4 = open('version.m4', 'w')
        version_m4.write("m4_define([VERSION_NUMBER], [%s])\n" %
                         self.current_version())
        version_m4.close()

    def phase_autogen(self, *args):
        self.log.info('Executing phase: autogen')

        if not exists('autogen.sh') or \
           self.isnewer('tools/autogen.sh', 'autogen.sh'):
            shutil.copyfile('tools/autogen.sh', 'autogen.sh')

        self.execute('sh', 'tools/autogen.sh')

    def phase_aclocal(self, *args):
        self.log.info('Executing phase: aclocal')
        self.execute('aclocal', '-I', 'm4')

    def phase_autoconf(self, *args):
        self.log.info('Executing phase: autoconf')

        if not exists('configure.ac') or \
           self.isnewer('tools/configure.ac', 'configure.ac'):
            shutil.copyfile('tools/configure.ac', 'configure.ac')

        if not exists('Makefile.am') or \
           self.isnewer('tools/Makefile.am', 'Makefile.am'):
            shutil.copyfile('tools/Makefile.am', 'Makefile.am')

        reason = self.need_to_prepare_autotools()
        if reason:
            self.log.info('autogen.sh must be run ' + reason)
            self.phase_version()
            self.phase_autogen()
            self.phase_gettext()
            self.phase_aclocal()
        else:
            self.log.debug('autogen.sh does not need to be run')

    #########################################################################
    # Update local files with the latest information                        #
    #########################################################################

    def phase_submodule(self, *args):
        self.log.info('Executing phase: submodule')
        if self.git_working_tree():
            self.execute('git', 'submodule', 'init')
            self.execute('git', 'submodule', 'update')

    def phase_pull(self, *args):
        self.log.info('Executing phase: pull')
        if self.git_working_tree():
            self.execute('git', 'pull')
            self.phase_submodule()

    #########################################################################
    # Automatic installation of build dependencies                          #
    #########################################################################

    def phase_dependencies(self, *args):
        self.log.info('Executing phase: dependencies')

        self.log.info("Installing Ledger's build dependencies ...")

        system = self.get_stdout('uname', '-s')

        if system == 'Darwin':
            if exists('/opt/local/bin/port'):
                self.log.info('Looks like you are using MacPorts on OS X')
                packages = [
                    'sudo', 'port', 'install', '-f',
                    'automake', 'autoconf', 'libtool',
                    'python26', 'boost-jam',
                    'libiconv', '+universal', 'zlib', '+universal',
                    'gmp' ,'+universal', 'mpfr', '+universal',
                    'ncurses', '+universal', 'ncursesw', '+universal',
                    'gettext' ,'+universal', 'libedit' ,'+universal',
                    'boost', '+universal+st+debug+python26+doc',
                    'cppunit' ,'+universal',
                    'texlive', 'doxygen', 'graphviz', 'texinfo',
                    'lcov', 'sloccount'
                ]
                self.log.info('Executing: ' + string.join(packages, ' '))
                self.execute(*packages)
            elif exists('/sw/bin/fink'):
                self.log.info('Looks like you are using Fink on OS X')
                self.log.error("I don't know the package names for Fink yet!")

        elif system == 'Linux':
            if exists('/etc/issue'):
                issue = open('/etc/issue')
                if issue.readline().startswith('Ubuntu'):
                    self.log.info('Looks like you are using APT on Ubuntu')
                    packages = [
                        'sudo', 'apt-get', 'install',
                         'build-essential',
                         'libtool', 'autoconf', 'automake',
                         'zlib1g-dev', 'libbz2-dev', 'python-dev',
                         'libboost1.35-dev',
                         'libboost-python1.35-dev',
                         'libboost-regex1.35-dev',
                         'libboost-date-time1.35-dev',
                         'libboost-filesystem1.35-dev'
                         'libgmp3-dev', 'libmpfr-dev', 'gettext',
                         'libedit-dev', 'libcppunit-dev',
                         #'texlive-full',
                         #'doxygen', 'graphviz', 'texinfo',
                         'lcov', 'sloccount'
                    ]
                    self.log.info('Executing: ' + string.join(packages, ' '))
                    self.execute(*packages)

            if exists('/etc/redhat-release'):
                release = open('/etc/redhat-release')
                if release.readline().startswith('CentOS'):
                    self.log.info('Looks like you are using YUM on CentOS')
                    packages = [
                        'sudo', 'yum', 'install', 'gcc', 'gcc-c++',
                        'compat-gcc-*', 'make', 'libtool', 'autoconf',
                        'automake', 'zlib-devel', 'bzip2-devel',
                        'python-devel', 'bboost-devel',
                         'gmp-devel', 'gettext-devel',
                         #'mpfr-devel'
                         'libedit-devel',
                         'cppunit-devel',
                         #'texlive-full',
                         #'doxygen', 'graphviz', 'texinfo',
                         #'lcov', 'sloccount'
                    ]
                    self.log.info('Executing: ' + string.join(packages, ' '))
                    self.execute(*packages)

    def phase_buildlibs(self, *args):
        self.log.info('Executing phase: buildlibs')

        try:
            os.chdir('lib')

            environ, conf_args = self.configure_environment()

            boost   = 'boost_1_40_0'
            tarball = boost + '.tar.bz2'

            if not exists(boost):
                if not exists(tarball):
                    self.log.info('Downloading Boost source tarball ...')
                    self.execute('curl', '-L', '-o', tarball,
                                 'http://downloads.sourceforge.net/boost/' +
                                 boost + '.tar.bz2?use_mirror=ufpr')

                if not exists(tarball):
                    self.log.error('Failed to locate the Boost source tarball')
                    sys.exit(1)

                fd = open(tarball)
                try:
                    csum = hashlib.md5()
                except:
                    csum = md5.md5()
                csum.update(fd.read())
                fd.close()
                digest = csum.hexdigest()

                if digest != 'a17281fd88c48e0d866e1a12deecbcc0':
                    self.log.error('Boost source tarball fails to match checksum')
                    sys.exit(1)

                self.log.info('Extracting Boost source tarball ...')
                self.execute('tar', 'xjf', tarball)

            if not exists(boost):
                self.log.error('Failed to locate the Boost sources')
                sys.exit(1)

            if not exists('cppunit') and self.git_working_tree():
                self.execute('git', 'clone', 'git://github.com/jwiegley/cppunit.git')

            if not exists('cppunit'):
                self.log.error('Failed to locate the CppUnit sources')
                sys.exit(1)

            self.execute('make',
                         'BOOST_SOURCE=%s' % boost,
                         'CC=%s'  % environ['CC'],
                         'CXX=%s' % environ['CXX'],
                         'LD=%s'  % environ['LD'],
                         'build-all')
        finally:
            os.chdir(self.source_dir)

    #########################################################################
    # Determine the system's basic configuration                            #
    #########################################################################

    def locate_boost_in_dir(self, path):
        if exists(path) and isdir(path):
            entries = os.listdir(path)
            entries.sort()
            for entry in entries:
                if re.search('boost_regex', entry):
                    self.log.info('Found a Boost library: ' + entry)

                match = re.match('libboost_regex([^.]*)\.(a|so|dylib)', entry)
                if match:
                    suffix = match.group(1)
                    self.log.info('Found Boost suffix => ' + suffix)
                    self.envvars['BOOST_HOME'] = dirname(path)
                    return suffix
        else:
            self.log.debug('The directory "%s" is not valid, skipping' %
                           path)
        return None

    def locate_boost(self):
        if self.envvars['BOOST_SUFFIX']:
            self.log.info(("Not looking for Boost, since " +
                            "a suffix of '%s' was given") %
                           self.envvars['BOOST_SUFFIX'])
        else:
            suffix = None
            for path in ['/usr/local/lib', '/opt/local/lib',
                         '/sw/lib', '/usr/lib']:
                self.log.info('Looking for Boost in %s...' % path)
                suffix = self.locate_boost_in_dir(path)
                if suffix is not None:
                    self.log.info('Boost is located here:')
                    self.log.info('BOOST_HOME    => ' +
                                   self.envvars['BOOST_HOME'])
                    self.log.info('BOOST_SUFFIX  => ' + suffix)
                    break
            if suffix is None:
                self.log.error("Boost could not be found.")
            self.envvars['BOOST_SUFFIX'] = suffix
        return self.envvars['BOOST_SUFFIX']

    def setup_system_directories(self):
        boost_suffix = self.locate_boost()

        # Each of these becomes '-isystem <name>'
        for path in ['/usr/local/include',
                        '%s/include/boost' %
                        self.envvars['BOOST_HOME'],
                        '%s/include' % self.envvars['BOOST_HOME'],
                        '%s/include/python%s' %
                        (self.envvars['PYTHON_HOME'],
                         self.envvars['PYTHON_VERSION'].strip()),
                        '/opt/local/include',
                        '/sw/include']:
            if exists(path) and isdir(path) and \
               path != '/usr/include':
                self.log.info('Noticing include directory => ' + path)
                self.sys_include_dirs.append(path)

        # Each of these becomes '-L<name>'
        for path in ['/usr/local/lib',
                     '%s/lib' % self.envvars['PYTHON_HOME'],
                     '%s/lib/python%s/config'
                     % (self.envvars['PYTHON_HOME'],
                        self.envvars['PYTHON_VERSION'].strip()),
                     '/opt/local/lib',
                     '/sw/lib']:
            if exists(path) and isdir(path):
                self.log.info('Noticing library directory => ' + path)
                self.sys_library_dirs.append(path)

    def setup_for_johnw(self):
        # jww (2009-03-09): Some peculiarities specific to my system
        if exists('/Users/johnw/Dropbox/Accounts/ledger.dat'):
            if self.current_flavor == 'debug':
                if exists('/usr/local/stow/cppunit/include'):
                    self.sys_include_dirs.insert(0, '/usr/local/stow/cppunit/include')
                    self.sys_library_dirs.insert(0, '/usr/local/stow/cppunit/lib')

                if exists('/usr/local/stow/icu/include'):
                    self.sys_include_dirs.insert(0, '/usr/local/stow/icu/include')
                    self.sys_library_dirs.insert(0, '/usr/local/stow/icu/lib')

                self.CPPFLAGS.append('-D_GLIBCXX_FULLY_DYNAMIC_STRING=1')

                self.options.use_glibcxx_debug = True

            self.CXXFLAGS.append('-march=nocona')
            self.CXXFLAGS.append('-msse3')

            self.configure_args.append('--disable-shared')
            self.configure_args.append('--enable-doxygen')
            self.configure_args.append('--enable-python')

            self.locate_my_libraries()

    def setup_for_system(self):
        self.setup_system_directories()

        system = self.get_stdout('uname', '-s')

        self.log.info('System type is => ' + system)

        # These options are global defaults at the moment
        #self.option_warn()
        if not self.no_pch:
            self.option_pch()

        if system == 'Linux':
            arch = self.get_stdout('uname', '-m')
            if arch == 'x86_64':
                if '--disable-shared' in self.configure_args:
                    self.configure_args.remove('--disable-shared')
                self.configure_args.append('--disable-static')
            self.CXXFLAGS.append('-pthread')

        elif system == 'Solaris':
            self.CXXFLAGS.append('-pthread')

        elif system == 'Darwin':
            #self.ARCHFLAGS += ['-arch', 'i386', '-arch', 'ppc',
            #                   '-isysroot', '/Developer/SDKs/MacOSX10.5.sdk']
            #self.LDARCHFLAGS += self.ARCHFLAGS
            #ldflag = '-Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk'
            #self.LDARCHFLAGS.append(ldflag)

            self.setup_for_johnw()

            # g++ 4.0.1 cannot use PCH headers on OS X 10.5, so we must use a
            # newer version.
            if exists('/opt/local/bin/g++-mp-4.4'):
                self.envvars['CC']  = '/opt/local/bin/gcc-mp-4.4'
                self.envvars['CXX'] = '/opt/local/bin/g++-mp-4.4'
                self.envvars['LD']  = '/opt/local/bin/g++-mp-4.4'
            elif exists('/opt/local/bin/g++-mp-4.3'):
                self.envvars['CC']  = '/opt/local/bin/gcc-mp-4.3'
                self.envvars['CXX'] = '/opt/local/bin/g++-mp-4.3'
                self.envvars['LD']  = '/opt/local/bin/g++-mp-4.3'
            elif not self.options.use_glibcxx_debug and \
                 exists('/usr/bin/g++-4.2'):
                self.envvars['CC']  = '/usr/bin/gcc-4.2'
                self.envvars['CXX'] = '/usr/bin/g++-4.2'
                self.envvars['LD']  = '/usr/bin/g++-4.2'
            else:
                self.option_no_pch()

        if '--enable-pch' not in self.configure_args and \
           (exists('/opt/local/bin/ccache') or \
            exists('/usr/local/bin/ccache')):
            self.envvars['CC']  = 'ccache ' + self.envvars['CC']
            self.envvars['CXX'] = 'ccache ' + self.envvars['CXX']
            self.envvars['LD']  = 'ccache ' + self.envvars['LD']

    def setup_flags(self):
        for path in self.sys_include_dirs:
            self.CPPFLAGS.append('-isystem')
            self.CPPFLAGS.append(path)

        self.CXXFLAGS.append('-pipe')

        for path in self.sys_library_dirs:
            self.LDFLAGS.append('-L' + path)

    def setup_flavor(self):
        self.setup_for_system()

        if not PrepareBuild.__dict__.has_key('setup_flavor_' +
                                             self.current_flavor):
            self.log.error('Unknown build flavor "%s"' % self.current_flavor)
            sys.exit(1)

        self.log.info('Setting up build flavor => ' + self.current_flavor)
        PrepareBuild.__dict__['setup_flavor_' + self.current_flavor](self)

        self.setup_flags()

    def escape_string(self, data):
        return re.sub('(["\\\\])', '\\\\\\1', data)

    def finalize_config(self):
        self.setup_flavor()

        for var in ('CPPFLAGS', 'CCFLAGS', 'ARCHFLAGS',
                    'CXXFLAGS', 'LDFLAGS', 'LDARCHFLAGS'):
            value = self.__dict__[var]
            if value:
                first = not self.envvars[var]
                for member in value:
                    #escaped = self.escape_string(member)
                    #if member != escaped:
                    #    member = escaped
                    if first:
                        first = False
                    else:
                        self.envvars[var] += ' '
                    self.envvars[var] += member
                self.log.debug('Final value of %s: %s' %
                               (var, self.envvars[var]))

            elif self.envvars.has_key(var):
                del self.envvars[var]

    #########################################################################
    # Options that can modify any build flavor                              #
    #########################################################################

    def option_pch(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --pch')

        self.configure_args.append('--enable-pch')

        self.CXXFLAGS.append('-fpch-deps')
        self.CXXFLAGS.append('-Wconversion')
        #self.CXXFLAGS.append('-Wold-style-cast')

        system = self.get_stdout('uname', '-s')

    def option_no_pch(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --no-pch')

        self.no_pch = True

        if '--enable-pch' in self.configure_args:
            self.configure_args.remove('--enable-pch')

        if '-Wconversion' in self.configure_args:
            self.CXXFLAGS.remove('-Wconversion')
        #if '-Wold-style-cast' in self.configure_args:
        #    self.CXXFLAGS.remove('-Wold-style-cast')

        system = self.get_stdout('uname', '-s')

        if system == "Darwin":
            self.envvars['CC']  = 'gcc'
            self.envvars['CXX'] = 'g++'
            self.envvars['LD']  = 'g++'

    def option_force(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --force')
        self.force = True

    def option_warn(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --warn')
	self.CXXFLAGS.append('-ansi')
	self.CXXFLAGS.append('-pedantic')
	self.CXXFLAGS.append('-pedantic-errors')
	self.CXXFLAGS.append('-Wall')
	self.CXXFLAGS.append('-Winvalid-pch')
	self.CXXFLAGS.append('-Wextra')
	self.CXXFLAGS.append('-Wcast-align')
	self.CXXFLAGS.append('-Wcast-qual')
	self.CXXFLAGS.append('-Wfloat-equal')
	self.CXXFLAGS.append('-Wmissing-field-initializers')
	self.CXXFLAGS.append('-Wno-endif-labels')
	self.CXXFLAGS.append('-Woverloaded-virtual')
	self.CXXFLAGS.append('-Wsign-compare')
	self.CXXFLAGS.append('-Wsign-promo')
	self.CXXFLAGS.append('-Wstrict-null-sentinel')
	self.CXXFLAGS.append('-Wwrite-strings')
	self.CXXFLAGS.append('-Wno-old-style-cast')
	self.CXXFLAGS.append('-Wno-deprecated')
	self.CXXFLAGS.append('-Wno-strict-aliasing')
	self.CXXFLAGS.append('-Werror')

    def option_boost(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --boost')
        self.envvars['BOOST_SUFFIX'] = value

    def option_pic(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --pic')
	self.CXXFLAGS.append('-fPIC')

    def option_output(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --output')
        self.build_dir = value

    def option_products(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --products')
        self.products_dir = value

    def option_local(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --local')
        self.build_dir = self.source_dir

    def option_trees(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --trees')
        self.build_dir = None

    def option_release(self, option=None, opt_str=None, value=None, parser=None):
        self.log.debug('Saw option --release')
        if '--disable-shared' in self.configure_args:
            self.configure_args.remove('--disable-shared')
        self.configure_args.append('--disable-dependency-tracking')

    def option_help(self, option=None, opt_str=None, value=None, parser=None):
        self.phase_help()

    #########################################################################
    # The various build flavors                                             #
    #########################################################################

    def locate_my_libraries(self):
        if self.options.use_glibcxx_debug:
            self.log.debug('We are using GLIBCXX_DEBUG, so setting up flags')

            self.CPPFLAGS.append('-D_GLIBCXX_DEBUG=1')

            if exists('/usr/local/stow/cppunit-debug/include'):
                if '/usr/local/stow/cppunit/include' in self.sys_include_dirs:
                    self.sys_include_dirs.remove('/usr/local/stow/cppunit/include')
                    self.sys_library_dirs.remove('/usr/local/stow/cppunit/lib')

                self.sys_include_dirs.insert(0, '/usr/local/stow/cppunit-debug/include')
                self.sys_library_dirs.insert(0, '/usr/local/stow/cppunit-debug/lib')

            if exists('/usr/local/stow/icu-debug/include'):
                if '/usr/local/stow/icu/include' in self.sys_include_dirs:
                    self.sys_include_dirs.remove('/usr/local/stow/icu/include')
                    self.sys_library_dirs.remove('/usr/local/stow/icu/lib')

                self.sys_include_dirs.insert(0, '/usr/local/stow/icu-debug/include')
                self.sys_library_dirs.insert(0, '/usr/local/stow/icu-debug/lib')

            if exists('/opt/local/lib/libboost_regex-d.a'):
                self.envvars['BOOST_HOME']   = '/opt/local'
                self.envvars['BOOST_SUFFIX'] = '-d'

                self.log.info('Setting BOOST_SUFFIX  => %s' %
                              self.envvars['BOOST_SUFFIX'])

                self.sys_include_dirs.append('/opt/local/include/boost')

            elif exists('/usr/local/lib/libboost_regex-xgcc44-sd-1_40.a'):
                self.envvars['BOOST_HOME']   = '/usr/local'
                self.envvars['BOOST_SUFFIX'] = '-xgcc44-sd-1_40'
                self.log.info('Setting BOOST_SUFFIX  => %s' %
                              self.envvars['BOOST_SUFFIX'])

                self.sys_include_dirs.append('/usr/local/include/boost-1_40')

            elif exists('/usr/local/lib/libboost_regex-xgcc44-d-1_40.a'):
                self.envvars['BOOST_HOME']   = '/usr/local'
                self.envvars['BOOST_SUFFIX'] = '-xgcc44-d-1_40'
                self.log.info('Setting BOOST_SUFFIX  => %s' %
                              self.envvars['BOOST_SUFFIX'])

                self.sys_include_dirs.append('/usr/local/include/boost-1_40')
        else:
            if exists('/opt/local/lib/libboost_regex.a'):
                self.envvars['BOOST_HOME']   = '/opt/local'
                self.envvars['BOOST_SUFFIX'] = ''

                self.log.info('Setting BOOST_SUFFIX  => %s' %
                               self.envvars['BOOST_SUFFIX'])

                self.sys_include_dirs.append('/opt/local/include/boost')

            elif exists('/usr/local/lib/libboost_regex-xgcc44-1_40.a'):
                self.envvars['BOOST_HOME']   = '/usr/local'
                self.envvars['BOOST_SUFFIX'] = '-xgcc44-1_40'
                self.log.info('Setting BOOST_SUFFIX  => %s' %
                              self.envvars['BOOST_SUFFIX'])

                self.sys_include_dirs.append('/usr/local/include/boost-1_40')

    def setup_flavor_default(self):
        pass

    def setup_flavor_debug(self):
        self.configure_args.append('--enable-debug')

        self.CXXFLAGS.append('-g')
        self.LDFLAGS.append('-g')

    def setup_flavor_opt(self):
        self.CXXFLAGS.append('-O3')
        self.LDFLAGS.append('-O3')
        self.CXXFLAGS.append('-fomit-frame-pointer')
        self.LDFLAGS.append('-fomit-frame-pointer')

    def setup_flavor_gcov(self):
        self.CXXFLAGS.append('-g')
        self.CXXFLAGS.append('-fprofile-arcs')
        self.CXXFLAGS.append('-ftest-coverage')
        self.LDFLAGS.append('-g')

    def setup_flavor_gprof(self):
        self.CXXFLAGS.append('-g')
        self.CXXFLAGS.append('-pg')
        self.LDFLAGS.append('-g')
        self.LDFLAGS.append('-pg')

    #########################################################################
    # Prettify the output from automake, by rewriting the Makefile          #
    #########################################################################

    def phase_patch(self, *args):
        """Alter the Makefile so that it's not nearly so verbose.

        This makes errors and warnings much easier to spot."""
        self.log.info('Executing phase: patch')

        if exists('Makefile'):
            self.log.debug('Patching generated Makefile')
            Makefile = open('Makefile')
            Makefile_new = open('Makefile.new', 'w')
            for line in Makefile.readlines():
                line = re.sub('^\t(\$\((LIBTOOL|CXX)\).*?\.(cc|cpp))$',
                              '\t@echo "   " CXX \$@;\\1 > /dev/null', line)
                line = re.sub('^\tmv -f', '\t@mv -f', line)
                line = re.sub('^\t\$\(am__mv\)', '\t@$(am__mv)', line)
                line = re.sub('^\t(\$\((.*?)LINK\).*)',
                              '\t@echo "    LD " \$@;\\1 > /dev/null', line)
                Makefile_new.write(line)
            Makefile_new.close()
            Makefile.close()

            os.remove('Makefile')
            os.rename('Makefile.new', 'Makefile')

            stamp = open('.timestamp', 'w')
            stamp.write('timestamp')
            stamp.close()

    #########################################################################
    # Configure build tree using autoconf                                   #
    #########################################################################

    def configure_environment(self):
        self.finalize_config()

        environ = dict(os.environ)
        for key, value in self.envvars.items():
            if value:
                environ[key] = value

        environ['PATH'] = ('%s/bin:%s' %
                           (environ['PYTHON_HOME'], environ['PATH']))

        if self.build_directory() == self.source_dir:
            conf_args = ['sh', 'configure']
        else:
            conf_args = ['sh', join(self.source_dir, 'configure'),
                         '--srcdir', self.source_dir]

        for var in ('CC', 'CPPFLAGS', 'CCFLAGS', 'ARCHFLAGS',
                    'CXX', 'CXXFLAGS', 'LD', 'LDFLAGS', 'LDARCHFLAGS'):
            if self.envvars.has_key(var) and self.envvars[var] and \
               (var.endswith('FLAGS') or exists(self.envvars[var])):
                conf_args.append('%s=%s' % (var, self.envvars[var]))

        if environ.has_key('BOOST_SUFFIX') and environ['BOOST_SUFFIX']:
            conf_args.append('--with-boost-suffix=%s' %
                             environ['BOOST_SUFFIX'])

        return (environ, conf_args + self.configure_args)

    def need_to_run_configure(self):
        Makefile = join(self.build_directory(), 'Makefile')
        if self.force:
            return 'because it was forced'
        elif not exists(Makefile):
            return 'because Makefile does not exist'
        elif self.isnewer(join(self.source_dir, 'configure'), Makefile):
            return 'because configure is newer than Makefile'
        elif self.isnewer(join(self.source_dir, 'Makefile.in'), Makefile):
            return 'because Makefile.in is newer than Makefile'
        return False

    def phase_configure(self, *args):
        self.log.info('Executing phase: configure')

        self.configured = True

        build_dir = self.ensure(self.build_directory())
        try:
            os.chdir(build_dir)

            reason = self.need_to_run_configure()
            if reason:
                self.log.info('./configure must be run ' + reason)
                self.log.debug('Source => ' + self.source_dir)
                self.log.debug('Build  => ' + build_dir)

                environ, conf_args = self.configure_environment()
                for arg in args:
                    if arg: conf_args.append(arg)

                self.log.debug('configure env  => ' + str(environ))
                self.log.debug('configure args => ' + str(conf_args))

                configure = Popen(conf_args, shell=False, env=environ)
                retcode = configure.wait()
                if retcode < 0:
                    self.log.error("Child was terminated by signal", -retcode)
                    sys.exit(1)
                elif retcode != 0:
                    self.log.error("Execution failed: " + string.join(conf_args, ' '))
                    sys.exit(1)

                if not self.options.no_patch:
                    self.phase_patch()
            else:
                if not self.options.no_patch and \
                   self.isnewer('Makefile', '.timestamp'):
                    self.phase_patch()

                self.log.debug('configure does not need to be run')

        finally:
            os.chdir(self.source_dir)

    def phase_config(self, *args):
        self.log.info('Executing phase: config')
        self.phase_submodule()
        self.phase_autoconf()
        self.phase_configure(*args)
        if self.should_clean:
            self.phase_clean()

    #########################################################################
    # Builds products from the sources                                      #
    #########################################################################

    def phase_make(self, *args):
        self.log.info('Executing phase: make')

        config_args = []
        make_args   = []

        for arg in args:
            if arg.startswith('--'):
                config_args.append(arg)
            else:
                make_args.append(arg)

        if self.options.jobs > 1:
            make_args.append('-j%d' % self.options.jobs)

        self.log.debug('Configure arguments => ' + str(config_args))
        self.log.debug('Makefile arguments  => ' + str(make_args))

        if not self.configured:
            self.phase_config(*config_args)

        build_dir = self.ensure(self.build_directory())
        try:
            self.log.debug('Changing directory to ' + build_dir)
            os.chdir(build_dir)

            self.execute(*(['make'] + make_args))
        finally:
            os.chdir(self.source_dir)

    def phase_update(self, *args):
        self.log.info('Executing phase: update')
        self.phase_pull()
        self.phase_make(*args)

    #########################################################################
    # Build directory cleaning phases                                       #
    #########################################################################

    def phase_clean(self, *args):
        self.log.info('Executing phase: clean')
        self.phase_make('clean')

    def phase_distclean(self, *args):
        self.log.info('Executing phase: distclean')
        self.phase_make('distclean')

    def phase_gitclean(self, *args):
        self.log.info('Executing phase: gitclean')
        if self.git_working_tree():
            self.execute('git', 'clean', '-dfx')

    #########################################################################
    # Packaging phases                                                      #
    #########################################################################

    def translate_file(self, path, dest):
        dest_file = join(dest, basename(path))

        self.log.debug("Translating file %s -> %s" % (path, dest_file))

        if not exists(dest_file):
            shutil.copyfile(path, dest_file)
            os.chmod(dest_file, 0755)

        for line in self.get_stdout('otool', '-L', dest_file).split('\n'):
            match = re.search('/opt/local/lib/(.+?)\.dylib', line)
            if not match:
                continue
            
            lib  = match.group(0)
            base = basename(lib)

            if lib != path:
                self.translate_file(lib, dest)

            self.execute('install_name_tool', '-change', lib,
                         '@loader_path/' + base, dest_file)

    def phase_bindmg(self, *args):
        self.log.info('Executing phase: bindmg')

        self.phase_make()

        binary  = join(self.build_directory(), 'ledger')
        if not exists(binary):
            self.log.error('Failed to build Ledger: ' + binary)
            sys.exit(1)

        cwd     = os.getcwd()
        tempdir = tempfile.mkdtemp()
        try:
            name   = 'ledger-%s' % self.current_version()
            dest   = join(tempdir, name)

            os.makedirs(dest)
            self.translate_file(binary, dest)

            self.execute('hdiutil', 'create', '-srcfolder', dest,
                         '-ov', join(cwd, name + '.dmg'))
        finally:
            os.chdir(cwd)
            shutil.rmtree(tempdir)

    def phase_upload(self, *args):
        self.log.info('Executing phase: upload')

        self.phase_bindmg()

        dmg = 'ledger-%s.dmg' % self.current_version()
        self.execute('zip', '%s.zip' % dmg, dmg)
        dmg = '%s.zip' % dmg

        self.execute('ssh', 'jw', 'rm', '-f', '/srv/ftp/pub/ledger/ledger-*.dmg.zip')
        self.execute('scp', dmg, 'jw:/srv/ftp/pub/ledger')
        self.execute('ssh', 'jw',
                     '(cd /srv/ftp/pub/ledger; ln -sf %s ledger-current.dmg.zip)' %
                     basename(dmg))

    #########################################################################
    # Other build phases                                                    #
    #########################################################################

    def configure_flavor(self, flavor, reset=True):
        self.initialize()               # reset everything
        self.build_dir      = None      # use the build/ tree
        self.current_flavor = flavor

        if reset and exists(self.build_directory()) and \
           isdir(self.build_directory()):
            self.option_release()

            self.log.info('=== Wiping build directory %s ===' %
                          self.build_directory())
            shutil.rmtree(self.build_directory())

    def phase_distcheck(self, *args):
        self.log.info('Executing phase: distcheck')
        
        self.configure_flavor('default', False)

        environ, conf_args = self.configure_environment()

        configure_args = []

        skip_next = False
        for arg in conf_args[2:]: # skip "sh configure"
            if arg == '--srcdir':
                skip_next = True
            elif skip_next:
                skip_next = False
            else:
                configure_args.append('"' + self.escape_string(arg) + '"')

        make_args = ['DISTCHECK_CONFIGURE_FLAGS=%s' %
                     string.join(configure_args, ' '), 'distcheck']

        self.log.debug('make_args for distcheck => ' + str(make_args))

        self.phase_make(*make_args)

    def phase_rsync(self, *args):
        self.log.info('Executing phase: rsync')

        source_copy_dir = join(self.ensure(self.products_directory()),
                               'ledger-proof')

        self.execute('rsync', '-a', '--delete',
                     '--exclude=.git/', '--exclude=b/',
                     '%s/' % self.source_dir, '%s/' % source_copy_dir)

        self.source_dir = source_copy_dir
        
    def phase_proof(self, *args):
        self.log.info('Executing phase: proof')

        self.log.info('=== Copying source tree ===')
        self.phase_rsync()
        self.phase_makeall(reset=True, *args)

        self.configure_flavor('opt')
        self.log.info('=== Testing opt ===')
        self.phase_make('fullcheck')
        
        self.configure_flavor('gcov')
        self.log.info('=== Testing gcov ===')
        self.phase_make('check')
        
        self.configure_flavor('debug')
        self.log.info('=== Testing debug ===')
        self.phase_make('fullcheck')
        
        self.configure_flavor('default')
        self.log.info('=== Testing default ===')
        self.phase_make('fullcheck')

        self.log.info('=== Building final distcheck ===')
        self.phase_distcheck()

    def phase_makeall(self, reset=False, *args):
        self.log.info('Executing phase: makeall')

        self.configure_flavor('opt', reset)
        
        system_hh_gch = join(self.source_dir, 'src', 'system.hh.gch')
        if exists(system_hh_gch):
            os.remove(system_hh_gch)

        self.log.info('=== Building opt ===')
        self.phase_make(*args)
        
        self.configure_flavor('gcov', reset)

        system_hh_gch = join(self.source_dir, 'src', 'system.hh.gch')
        if exists(system_hh_gch):
            os.remove(system_hh_gch)

        self.log.info('=== Building gcov ===')
        self.phase_make(*args)
        
        system_hh_gch = join(self.source_dir, 'src', 'system.hh.gch')
        if exists(system_hh_gch):
            os.remove(system_hh_gch)

        self.log.info('=== Building default ===')
        self.phase_make(*args)

        self.configure_flavor('debug', reset)

        system_hh_gch = join(self.source_dir, 'src', 'system.hh.gch')
        if exists(system_hh_gch):
            os.remove(system_hh_gch)

        self.log.info('=== Building debug ===')
        self.phase_make(*args)
        
        self.configure_flavor('default', reset)

    #########################################################################
    # Help                                                                  #
    #########################################################################

    def phase_help(self, *args):
        self.option_parser.print_help()

        print """ 
Of the optional ARGS, the first is an optional build FLAVOR, with the default
being 'debug':

  default           Regular autoconf settings
  debug             Debugging and --verify support (default)
  opt               Full optimizations
  gcov              Coverage analysis
  gprof             Code profiling (for OS X, just use: 'shark -i ledger ...')

Next is the optional build PHASE, with 'config' being the default:

  clean             Runs 'make clean' in the build directory
  config            Configure the environment for building
  dependencies      Automatically install all necessary build dependencies
  distcheck         Properly does 'make distcheck', carrying all flags
  distclean         Runs 'make distclean' in the build directory
  gitclean          Runs 'git clean -dfx', which *really* cleans things
  help              Displays this help text
  info              Show information about the build environment
  make              Do a make in the build directory
  proof             Proves Ledger by building and testing every flavor
  pull              Pulls the latest, and updates local config if need be
  update            Does it all, updates your environment and re-make's

There are many other build phases, though most are not of interest to the
typical user:

  aclocal           Runs aclocal -I m4
  autoconf          Prepare the autoconf subsystem
  autogen           Runs autogen.sh
  configure         Runs just ./configure
  do_all            Runs makeall followed by proof
  gettext           Initialize gettext support
  makeall           Build every flavor there is
  patch             Patches the automake Makefile to be less verbose
  products          Report the products directory path
  rsync             Rsync a copy of the source tree into Products
  sloc              Report total Source Lines Of Code
  submodule         Updates Git submodules (better to use 'pull')
  version           Output current HEAD version to version.m4

NOTE: If you wish to pass options to configure or make, add "--" followed by
your options.  Here are some real-world examples:

  ./acprep
  ./acprep opt -- make -j3
  ./acprep -- --enable-doxygen"""
        sys.exit(0)

PrepareBuild().run()