view bin/gub @ 5481:df03d0971b5d

Logging nits.
author Jan Nieuwenhuizen <janneke@gnu.org>
date Thu, 20 Aug 2009 14:35:30 +0200
parents b3782861ff7e
children b685562acfe2
line wrap: on
line source

#! /usr/bin/env python

"""
    Copyright (c) 2005--2008
    Jan Nieuwenhuizen <janneke@gnu.org>
    Han-Wen Nienhuys <hanwen@xs4all.nl>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

def argv0_relocation ():
    import os, sys
    bindir = os.path.dirname (sys.argv[0])
    prefix = os.path.dirname (bindir)
    if not prefix:
        prefix = bindir + '/..'
    sys.path.insert (0, prefix)
    if sys.path[0] == 'gbin/..':
        sys.path[0] = '/'
        os.getcwd = lambda: '/'

argv0_relocation ()

import optparse
import os
import sys
#
from gub import buildrunner
from gub import configure
from gub import cross
from gub import gup
from gub import locker
from gub import logging
from gub import misc
from gub import repository
from gub.syntax import printf

import gub.settings   # otherwise naming conflict with settings local vars.

def get_cli_parser ():
    p = optparse.OptionParser ()

    p.usage='''gub [OPTION]... [PACKAGE]...'''

    p.description='Grand Unified Builder.'

    examples = '''
Examples:

  gub lilypond

  gub darwin-x86::lilypond

  gub freebsd-64::ftp://ftp.gnu.org/pub/gnu/bison/bison-2.3.tar.gz

  gub mingw::openoffice
'''

    def format_examples (self):
        return examples
    if 'epilog'  in  p.__dict__:
        p.formatter.format_epilog = format_examples
        p.epilog = examples
    else:
        p.formatter.format_description = format_examples
        p.description = examples

    p.add_option ('-k', '--keep', action='store_true',
                  dest='keep_build',
                  default=None,
                  help='Leave build and src dir for inspection')

    p.add_option ('-s', '--src-package', action='store_true',
                  default=None,
                  help='Also roll source packages')

    p.add_option ('-p', '--platform', action='store',
                  type='choice',
                  default=None,
                  help='select target platform',
                  choices=list (gub.settings.platforms.keys ()))

    p.add_option ('--inspect', action='store',
                  dest='inspect_key',
                  default=None,
                  help='Key of package to inspect')

    p.add_option ('--inspect-output', action='store',
                  default=None,
                  help='Where to write result of inspection')

    p.add_option ('--download-only', action='store_true',
                  default=False,
                  help='Exit after downloading.')

    p.add_option ('--offline', action='store_true',
                  dest='offline',
                  default=False,
                  help='Do not attempt to download anything')

    p.add_option ('--online', action='store_false',
                  dest='offline',
                  default=False,
                  help='Download as part of the build')

    def set_stage (option, opt_str, value, parser):
        if value == 'download':
            parser.values.download_only = True
        else:
            parser.values.stage = value

    p.add_option ('--stage', action='callback', callback=set_stage,
                  dest='stage', type='string', default=None,
                  help='Force rebuild of stage')

    p.add_option ('--fresh', action='store_true',
                  dest='fresh', default=False,
                  help='Restart all builds')

    p.add_option ('--cross-distcc-host', action='append',
                  dest='cross_distcc_hosts', default=[],
                  help='Add another cross compiling distcc host')

    p.add_option ('--native-distcc-host', action='append',
                  dest='native_distcc_hosts', default=[],
                  help='Add another native distcc host')

    p.add_option ('-v', '--verbose', action='count', dest='verbosity', default=0)

    p.add_option ('-q', '--quiet', action='count', dest='quiet', default=0)

    p.add_option ('--lilypond-versions', action='store',
                  default='uploads/lilypond.versions',
                  dest='lilypond_versions')

    p.add_option ('--force-package', action='store_true',
                  default=False,
                  help='allow packaging of tainted compiles' )

    p.add_option ('--build-source', action='store_true',
                  default=False,
                  help='build source packages')

    p.add_option ('--lax-checksums',
                  action='store_true',
                  default=False,
                  help="do not rebuild packages with failing checksums")

    p.add_option ('-n', '--dry-run',
                  action='store_true',
                  default=False,
                  help='Print action, do not run.')

    p.add_option ('--show-dependencies',
                  action='store_true',
                  default=False,
                  help='Print dependencies.')

    p.add_option ('-x', '--no-dependencies',
                  action='store_true',
                  default=False,
                  help='Ignore dependencies.')

    p.add_option ('-l', '--skip-if-locked',
                  default=False,
                  action="store_true",
                  help="Return successfully if another build is already running")

    p.add_option ('-j', '--jobs',
                  default="1", action='store',
                  help='set number of simultaneous jobs')

    return p

#FIXME: move to BuildRunner?
def inspect (settings, files):
    (names, specs) = gup.get_source_packages (settings, files)
    pm = gup.get_target_manager (settings)
    gup.add_packages_to_self.manager (pm, settings, specs)
    deps = [d for d in names if d in specs]

    for f in files:
        v =  pm.package_dict (f)[settings.options.inspect_key]
        if settings.options.inspect_output:
            open (settings.options.inspect_output, 'w').write (v)
        else:
            printf (v)

#FIXME: move to BuildRunner?
def build (settings, options, files):
    logging.stage ('calculating dependencies\n')
    (names, specs) = gup.get_source_packages (settings, files)
    if options.no_dependencies:
        names = list ()
        for spec in list (specs.values ()):
            if spec.name () in files or spec.platform_name () in files:
                if not names:
                    specs = dict ()
                names += [spec.platform_name ()]
                specs[spec.platform_name ()] = spec
    platform = settings.platform
    dep_str = ' '.join (names)
    if options.show_dependencies:
        logging.stage ('dependencies[%(platform)s]: %(dep_str)s\n' % locals ())
        sys.exit (0)
    dep_str.replace (misc.with_platform ('', platform), '')
    logging.info ('dependencies[%(platform)s]: %(dep_str)s\n' % locals ())

    if options.fresh:
        for spec in list (specs.values()):
            status = spec.get_stamp_file ()
            if os.path.exists (status):
                logging.info ('Removing status file: %(status)s\n' % locals ())
                os.unlink (status)

    # TODO: figure out how to patch src_package from
    # stages. Simpleminded solution does not work
    # and causes mingw runtime to run autogenmagic       

    # TODO: patch out clean if --keep was specified.

    # TODO: move taint check in special stage, and patch out --force
    # was specified.

    # TODO: patch out checksum comparison if --lax-checksum was specified.

    if options.download_only:
        options.offline = False
        
    if not options.offline:
        for name in names:
            specs[name].download ()
    if options.download_only:
        sys.exit (0)
        
    # FIXME: hw, why is this?  Doesn't this break 
    if options.stage:
        names = files

    try:
        manager = gup.DependencyManager (settings.system_root)
        ## Todo: have a readonly lock for tools platform
    except locker.LockedError:
        logging.error ('another build in progress.  Skipping.')
        if options.skip_if_locked:
            sys.exit (0)
        raise

    b = buildrunner.BuildRunner (manager, settings, options, specs)

    if options.show_dependencies:
        sys.exit (0)

    b.calculate_checksums ()
    if options.dry_run:
        for name in sorted (b.checksums):
            printf (b.checksums[name])
        sys.exit (0)
        
    configure.test_required (logging.default_logger.error)

    b.build_source_packages (names)

def exceptional_build (settings, options, files, logger):
    try:
        build (settings, options, files)
    except:
        t, v, b = sys.exc_info ()
        if not (t == SystemExit and not v.code):
            sys.stderr.write ('\n')
            logger.dump_tail (sys.stderr)
            sys.stderr.write ('\n')
            if t == misc.SystemFailed:
                # Log the python exception, and only print cluttering
                # output when using -v; what we want to see is the
                # command error.
                logger.write_log (misc.exception_string (), 'warning')
            else:
                # We have a python exception, print that.
                logger.write_log (misc.exception_string (), 'error')
            if buildrunner.target:
                sys.stderr.write ('*** Failed target: %s\n' % buildrunner.target)
            # Using "raise" here has this exception's stack trace at
            # the tail of GUB's output.  Most of the time, it's more
            # helpful [for users] to see the actual error, rather
            # than *where* it occured in our Python code.
            # The exception is still available in the build log,
            # and will be printed when using -v
            ## raise
            return 1
    logger.write_log ('done\n', 'stage')
    return 0

def logged_build (settings, options, files):
    logger = logging.set_default_log (settings.expand ('%(logdir)s/build.log'),
                                      options.verbosity)
    
    logger.write_log ('root: %s\n' % settings.system_root, 'verbose')
    logger.write_log ('platform: %s\n' % settings.platform, 'verbose')

    sys.exit (exceptional_build (settings, options, files, logger))

def main ():
    if not 'stat' in misc.librestrict ():
        os.environ['LIBRESTRICT'] = 'open:stat'
        printf ('non-stat restriction is obsolete, use')
        printf ('export LIBRESTRICT=open:stat')
#        sys.exit (2)

    os.environ = gub.settings.clean_environment ()
    # FIXME: pydb hack.  TODO: make gub run not from srcdir
    bindir = os.path.dirname (sys.argv[0])
    gubdir = os.path.dirname (bindir)
    if gubdir:
        os.chdir (gubdir)

    cli_parser = get_cli_parser ()
    (options, files) = cli_parser.parse_args ()
    options.verbosity -= options.quiet
    options.src_package = options.src_package or options.platform == 'cygwin'

    logging.default_logger.threshold = options.verbosity
    logging.info ('files: %(files)s\n' % locals ())
    logging.info ('CLEANED env: ' + str (os.environ) + '\n')

    if not options.platform and len (files):
        options.platform, x = misc.split_platform (files[0])

    settings = gub.settings.Settings (options.platform)

    if options.inspect_key:
        inspect (settings, files)
        sys.exit (0)

    if not files:
        logging.error ('error: nothing to do\n')
        cli_parser.print_help ()
        sys.exit (2)
        
    logged_build (settings, options, files)


if __name__ == '__main__':
    main ()