view bin/gub @ 6512:ccc20ae889ca default tip guix

mingw::guile-2.0.7 builds.
author Jan Nieuwenhuizen <janneke@gnu.org>
date Thu, 24 Mar 2016 08:03:39 +0100
parents 72b46c22bf99
children
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 pickle
import optparse
import os
import time
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 gub_log
from gub import misc
from gub import repository
from gub.syntax import printf
from gub import loggedos

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
'''

    misc.optparse_epilog (p, 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 ('-l', '--log',
                  default='package',
                  help='select log file granularity',
                  choices=['build','package','platform'])
    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 ('-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='versiondb/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 ('--skip-if-locked',
                  default=False,
                  action="store_true",
                  help="Return successfully if another build is already running")
    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):
    gub_log.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:
        printf ('dependencies[%(platform)s]: %(dep_str)s' % locals ())
        sys.exit (0)
    dep_str.replace (misc.with_platform ('', platform), '')
    gub_log.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):
                gub_log.info ('Removing status file: %(status)s\n' % locals ())
                os.unlink (status)

    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:
        gub_log.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 False and huh_what_is_this_aboutoptions.dry_run:
        for name in sorted (b.checksums):
            printf (b.checksums[name])
        sys.exit (0)
        
    configure.test_required (gub_log.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):
            log = buildrunner.logger
            if os.path.exists (log.log_file_name):
                sys.stderr.write ('\n')
                log.dump_tail (sys.stderr)
                sys.stderr.write ('\n')
            exception_verbosity = 'stage'
            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.
                exception_verbosity = 'warning'
            log.write_log (misc.exception_string (), exception_verbosity)
            if buildrunner.target:
                logger.write_log ('*** Failed target: %s\n' % buildrunner.target, 'stage')
            # 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):
    # EXPENSIVE: 
    #log_dir = settings.expand ('%(alllogdir)s')
    log_dir = settings.expand ('%(workdir)s/log')
    if not os.path.isdir (log_dir):
        os.makedirs (log_dir)
    log = os.path.join (log_dir, 'gub.log')
    if os.path.isfile (log):
        misc.rename_append_time (log)
    logger = gub_log.set_default_log (log, options.verbosity)
    buildrunner.logger = logger
    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 environment_sanity (settings):
    environment_file = settings.alltargetdir + '/environment.pickle'
    environment = dict ()
    if os.path.exists (environment_file):
        environment = dict (pickle.loads (open (environment_file, 'rb').read ()))
    # expand any ~ in the PATH
    os.environ['PATH'] = ":".join( map( lambda(x):os.path.expanduser(x),
                                        os.environ['PATH'].split(':')))
    differ = []
    for key in list (misc.uniq (sorted (environment.keys ()
                                        + os.environ.keys ()))):
        if key == 'ABI':
            continue # we don't care about changes to this
        if environment.get (key, None) != os.environ.get (key, None):
            differ += [key]
    if list (environment.keys ()) and differ:
        printf ('*** environment changed\n')
        for key in differ:
            printf ('    #new# %(key)s=' % locals () + os.environ.get (key, ''))
            if environment.get (key, None):
                printf ('    %(key)s=' % locals () + environment[key])
            else:
                printf ('    unset %(key)s' % locals ())
        sys.stderr.write ('\n*** press ^C in 10s or suffer a full rebuild')
        try:
             for i in range (10):
                time.sleep (1)
                sys.stderr.write ('.')
        except KeyboardInterrupt:
            sys.exit (1)
        except:
            pass
    open (environment_file, 'w').write (pickle.dumps (os.environ, protocol=2))

def main ():
    # stat restriction has become much more real using BASH
    # lots of specs do not compile atm
    if False and 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.build_source = options.build_source or options.platform == 'cygwin'
    if options.dry_run:
        loggedos.dry_run ()

    gub_log.default_logger.threshold = options.verbosity
    gub_log.info ('files: %(files)s\n' % locals ())
    gub_log.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:
        gub_log.error ('error: nothing to do\n')
        cli_parser.print_help ()
        sys.exit (2)
        
    environment_sanity (settings)
    logged_build (settings, options, files)


if __name__ == '__main__':
    main ()