view gub/gup.py @ 5459:9d90cca4c2a6

Plain [non-GUB/ BOOTSTRAP] build fixes.
author Jan Nieuwenhuizen <janneke@gnu.org>
date Mon, 17 Aug 2009 21:13:10 +0200
parents 29c354d0e78b
children aafa5a28314c
line wrap: on
line source

# tools python has no gdbm, breaks simple home python/lilypond build
# but dbhash seems to break in odd ways:
#  File "bsddb/dbutils.py", line 62, in DeadlockWrap
#  DBPageNotFoundError: (-30987, 'DB_PAGE_NOTFOUND: Requested page not found')

import fcntl
import glob
import inspect
import os
import pickle
import re
import string
import sys

#
from gub.syntax import printf
from gub import build
from gub import cross
from gub.db import db
from gub import dependency
from gub import locker
from gub import logging
from gub import loggedos
from gub import misc
import gub.settings
from gub import target

class GupException (Exception):
    pass

class FileManager:

    """FileManager handles a tree, and keeps track of files,
    associating files with a package name"""

    def __init__ (self, root, dbdir=None, clean=False):
        self.root = os.path.normpath (root)
        if dbdir:
            self.config = dbdir
        else:
            self.config = self.root + '/etc/gup'

        self.config = os.path.normpath (self.config)
        self.verbose = True
        self.is_distro = False

        ## lock must be outside of root, otherwise we can't rm -rf root
        # UGH, /GUB
        self.lock_file = self.root + '.lock'
        self.lock = locker.Locker (self.lock_file)
        if clean:
            loggedos.system (logging.default_logger,
                             'rm -fr %s' % self.config)
        self.make_dirs ()
        files_db = self.config + '/files.db'
        packages_db = self.config + '/packages.db'
        self._file_package_db = db.open (files_db, 'c')
        self._package_file_db = db.open (packages_db, 'c')
        #except DBInvalidArgError:
        # import gdmb
        # file_db = gdbm.open (file_db, 'c')
        # packages_db = gdbm.open (packages_db, 'c')
            
    def __repr__ (self):
        name = self.__class__.__name__
        root = self.root
        distro =  self.is_distro
        return '%(name)s: %(root)s, distro: %(distro)d'  % locals ()

    def make_dirs (self):
        if not os.path.isdir (self.config):
            loggedos.system (logging.default_logger,
                             'mkdir -p %s' % self.config)
        if not os.path.isdir (self.root):
            loggedos.system (logging.default_logger,
                             'mkdir -p %s' % self.root)
        
    def package_installed_files (self, name):
        return [file_name for file_name in self._package_file_db[name].decode ('utf8').split ('\n')]

    def installed_packages (self):
        return [name.decode ('utf8') for name in list (self._package_file_db.keys ())]

    def is_installed (self, name):
        return name in self.installed_packages ()

    def installed_files (self):
        return [file_name.decode ('utf8') for file_name in list (self._file_package_db.keys ())]

    def is_installed_file (self, name):
        return name in self.installed_files ()

    def install_tarball (self, ball, name, prefix_dir):
        logging.action ('untarring: %(ball)s\n' % locals ())

        _z = misc.compression_flag (ball)
        _v = '' # self.os_interface.verbose_flag ()
        lst = loggedos.read_pipe (logging.default_logger,
                                  'tar -t%(_z)s -f "%(ball)s"'
                                  % locals ()).split ('\n')
        conflicts = False
        installed_files = self.installed_files ()
        installed_files_string = ':'.join ([''] + installed_files + [''])
        misc.timing ()
        for f in lst:
            if (':' + f + ':' in installed_files_string
                and not os.path.isdir (os.path.join (self.root, f))):
                package = self._file_package_db[f]
                logging.error ('already have file %(f)s: %(package)s\n'
                               % locals ())
                conflicts = True
        logging.command ('GUP: for f in lst:' + misc.timing () + '\n')
        if conflicts and not self.is_distro:
            raise Exception ('abort')
        root = self.root
        loggedos.system (logging.default_logger,
                         # cd %(root)s to avoid open(2) of cwd, see
                         # http://lists.gnu.org/archive/html/lilypond-devel/2009-03/msg00304.html
                         'cd %(root)s && tar -C %(root)s -p -x%(_z)s%(_v)s -f %(ball)s'
                         % locals ())
        self._package_file_db[name] = '\n'.join (lst)
        for f in lst:
            # ignore directories.
            if not f.endswith ('/'):
                self._file_package_db[f] = name
            if f.endswith ('.la'):
                self.libtool_la_fixup (root, f)
            if f.endswith ('.pc'):
                self.pkgconfig_pc_fixup (root, f, prefix_dir)

    def libtool_la_fixup (self, root, file):
        # avoid using libs from build platform, by adding
        # %(system_root)s
        if file.startswith ('./'):
            file = file[2:]
        dir = os.path.dirname (file)
        loggedos.file_sub (logging.default_logger,
                           [('^libdir=.*',
                             """libdir='%(root)s/%(dir)s'""" % locals ()
                             ),],
                           '%(root)s/%(file)s' % locals (),
                           must_succeed=('tools/root' not in self.root
                                         and 'cross' not in dir
                                         and '/GUB' not in self.root))
        
    def pkgconfig_pc_fixup (self, root, file, prefix_dir):
        # avoid using libs from build platform, by adding
        # %(system_root)s
        if file.startswith ('./'):
            file = file[2:]
        dir = os.path.dirname (file)
        if '%' in prefix_dir or not prefix_dir:
            barf
        loggedos.file_sub (logging.default_logger,
                           [('(-I|-L) */usr',
                             '''\\1%(root)s%(prefix_dir)s''' % locals ()
                             ),],
                           '%(root)s/%(file)s' % locals ())

    def uninstall_package (self, name):
        logging.action ('uninstalling package: %s\n' % name)

        lst = self.package_installed_files (name)

        dirs = []
        files = []
        for i in lst:
            f = os.path.join (self.root, i)
            if os.path.islink (f):
                files.append (f)
            elif (not os.path.exists (f)
               and not self.is_distro):
                printf ('FileManager: uninstall: %s' % name)
                printf ('FileManager: no such file: %s' % f)
            elif os.path.isdir (f):
                dirs.append (f)
            else:
                files.append (f)

        if not 'BOOTSTRAP' in os.environ.keys ():
            # let's not remove everything from below ourselves...
            for f in files:
                os.unlink (f)

        for d in reversed (dirs):
            try:
                os.rmdir (d)
            except OSError:
                logging.verbose ('warning: %(d)s not empty\n' % locals ())
        for f in lst:
            ## fixme (?)  -- when is f == ''
            if not f or f.endswith ('/'):
                continue
            try:
                del self._file_package_db[f]
            except:
                printf ('db delete failing for ', f)
        del self._package_file_db[name]

class PackageDictManager:
    """
    A dict of PackageName ->  (Key->Value dict)

    which can be read off the disk.
    """
    def __init__ (self):
        self._packages = {}
        ## ugh mi 
        self.verbose = False
        
    def register_package_dict (self, d):
        nm = d['name']
        if 'split_name' in d:
            nm = d['split_name']
        if 0 and (nm in self._packages):
            if self._packages[nm]['spec_checksum'] != d['spec_checksum']:
                logging.info ('******** checksum of %s changed!\n\n' % nm)

            if self._packages[nm]['cross_checksum'] != d['cross_checksum']:
                logging.info ('******** checksum of cross changed for %s\n' % nm)
            return
        self._packages[nm] = d
        
    def register_package_header (self, package_hdr, branch_dict):
        if self.verbose:
            logging.info ('reading package header: %s\n'
                          % package_hdr.__repr__ ())

        str = open (package_hdr).read ()
        header_name = os.path.basename (package_hdr)
        d = dict (pickle.loads (str))
        name = d['basename']
        vc_branch = d.get ('vc_branch', '')

        if name in branch_dict:
            branch = branch_dict[name]
            if ':' in branch:
                (remote_branch, branch) = tuple (branch.split (':'))
            if branch != vc_branch:
                logging.error ('package of branch: %(vc_branch)s, expecting: %(branch)s\n' % locals ())
                logging.error ('ignoring header: %(header_name)s\n' % locals ())
                return
        elif d['vc_branch']:
            logging.error ('no branch for package: %(name)s\n' % locals ())
            logging.error ('ignoring header: %(header_name)s\n' % locals ())
            logging.error ('available branch: %(vc_branch)s\n' % locals ())
            return
        
        name = d['split_name']
        if 0:
          ## FIXME ?
          if name in self._package_dict_db:
              if str != self._package_dict_db[name]:
                  logging.info ("package header changed for %s\n" % name)

              return

        self.register_package_dict (d)

    def unregister_package_dict (self, name):
        del self._packages[name]

    def is_registered (self, name):
        return name in self._packages

    def package_dict (self, name):
        return self._packages.get (name, dict ())

    def available_packages (self):
        return list (self._packages.keys ())

    def get_all_packages (self):
        return list (self._packages.values ())

    def is_installable (self, name):
        d = self.package_dict (name)
        ball = '%(split_ball)s' % d
        hdr = '%(split_hdr)s' % d
        return os.path.exists (ball) and os.path.exists (hdr)

    def read_package_headers (self, s, branch_dict):
        if os.path.isdir (s) and not s.endswith ('/'):
            s += '/'
        for f in glob.glob ('%(s)s*hdr' % locals ()):
            self.register_package_header (f, branch_dict)


## FIXME: MI
class PackageManager (FileManager, PackageDictManager):
    """PackageManager is a FileManager, which also associates a
    key/value dict with each package.

    Such dicts come either from either 

    1. A build spec (ie. a python object)

    2. A pickled dict on disk, a package header

    3. 
    """

    
    def __init__ (self, root,  **kwargs):
        FileManager.__init__ (self, root, **kwargs)
        PackageDictManager.__init__ (self)
        
        dicts_db = self.config + '/dicts.db'
        self._package_dict_db = db.open (dicts_db, 'c')
        for k in list (self._package_dict_db.keys ()):
            v = self._package_dict_db[k]
            self.register_package_dict (pickle.loads (v))

    def installed_package_dicts (self):
        return [self._packages[n] for n in self.installed_packages ()]

    def install_package (self, name):
        if self.is_installed (name):
            return
        logging.action ('installing package: %s\n' % name)
        if self.is_installed (name):
            logging.error ('already have package: ' + name + '\n')
            raise Exception ('abort')
        d = self._packages[name]
        ball = '%(split_ball)s' % d
        self.install_tarball (ball, name, d['prefix_dir'])
        self._package_dict_db[name] = pickle.dumps (d, protocol=2)

    def uninstall_package (self, name):
        FileManager.uninstall_package (self, name)
        del self._package_dict_db[name]

    def source_name (self, name):
        return self._packages [name]['source_name']

    
class DependencyManager (PackageManager):

    """Manage packages that have dependencies and
    build_dependencies in their package dicts"""

    def __init__ (self, *args, **kwargs):
        PackageManager.__init__ (self, *args, **kwargs)
        self.include_build_deps = True

    def dependencies (self, name):
        assert type (name) == str
        try:
            return [misc.strip_platform (x)
                    for x in self.dict_dependencies (self._packages[name])]
        except KeyError:
            logging.error ('no such package: %(name)s\n' % locals ())
            return list ()

    def dict_dependencies (self, dict):
        deps = dict['dependencies_string'].split (';')
        if self.include_build_deps:
            deps += dict['build_dependencies_string'].split (';')
        deps = [d for d in deps if d]
        return deps


################
# UGh moveme


def topologically_sorted_one (todo, done, dependency_getter,
                              recurse_stop_predicate=None):
    sorted = []
    if todo in done:
        return sorted

    done[todo] = 1

    def type_equal (a, b):
        return ((type (a) == type (b))
                or inspect.isclass (type (a)) == inspect.isclass (type (b)))

    deps = dependency_getter (todo)
    for d in deps:
        if recurse_stop_predicate and recurse_stop_predicate (d):
            continue
        if not type_equal (d, todo):
            printf (type (d), '!=', type (todo))
            printf (d.__class__, todo.__class__)
            printf (d.__dict__, todo.__dict__)
            printf (inspect.isclass (type (d)), inspect.isclass (type (todo)))
            assert type_equal (a, b)
        sorted += topologically_sorted_one (d, done, dependency_getter,
                                            recurse_stop_predicate=recurse_stop_predicate)
    sorted.append (todo)
    return sorted

def topologically_sorted (todo, done, dependency_getter,
             recurse_stop_predicate=None):
    s = []
    for t in todo:
        s += topologically_sorted_one (t, done, dependency_getter,
                                       recurse_stop_predicate)
    return s


################################################################
# UGH
# this is too hairy. --hwn

def gub_to_distro_deps (deps, gub_to_distro_dict):
    distro = []
    for i in deps:
        if i in list (gub_to_distro_dict.keys ()):
            distro += gub_to_distro_dict[i]
        else:
            distro += [i]
    return distro

def get_base_package_name (name):
    name = re.sub ('-devel$', '', name)

    # breaks mingw dep resolution, mingw-runtime
    ##name = re.sub ('-runtime$', '', name)
    # breaks building of lilypond-doc package
    ##name = re.sub ('-doc$', '', name)
    return name

# FIXME: how to assign to outer var?
# FIXME: make this more plugin-ish
cygwin_resolver = None
debian_resolver = None

def get_source_packages (settings, const_todo):
    """TODO is a list of (source) builds.

    Generate a list of AutoBuild needed to build TODO, in
    topological order
    """

    # Do not confuse caller, do not modify caller's todo
    todo = const_todo[:]
    specs = dict ()
    sets = {settings.platform: settings}

    def with_platform (s, platform=settings.platform):
        return misc.with_platform (s, platform)

    def split_platform (u):
        return misc.split_platform (u, settings.platform)

    def name_to_dependencies_via_gub (url):
        platform, url = split_platform (url)
        if 'BOOTSTRAP' in os.environ.keys () and platform == 'tools':
            platform = settings.build_platform
        if ':' in url:
            base, unused_parameters = misc.dissect_url (url)
            name = (os.path.basename (base)
                    .replace ('.git', ''))
            key = url
        else:
            name = get_base_package_name (url)
            url = None
            key = name
            
        key = with_platform (key, platform)
        if key in specs:
            spec = specs[key]
        else:
            if platform not in sets:
                sets[platform] = gub.settings.Settings (platform)
            spec = dependency.Dependency (sets[platform], name, url).build ()
            specs[key] = spec
            
        return list (map (get_base_package_name, spec.get_platform_build_dependencies ()))

    def name_to_dependencies_via_distro (distro_packages, url):
        platform, url = split_platform (url)
        if ':' in url:
            base, unused_parameters = misc.dissect_url (url)
            name = (os.path.basename (base)
                    .replace ('.git', ''))
            key = url
        else:
            name = url #get_base_package_name (url)
            url = None
            key = name
            
        key = with_platform (key, platform)
        if key in specs:
            spec = specs[key]
        else:
            if name in todo or name not in list (distro_packages.keys ()):
                if platform not in sets:
                    sets[platform] = gub.settings.Settings (platform)
                spec = dependency.Dependency (sets[platform], name).build ()
            else:
                spec = distro_packages[name]
            specs[key] = spec
        return spec.get_platform_build_dependencies ()

    # FIXME: how to assign to outer var?
    # cygwin_resolver = None
    def name_to_dependencies_via_cygwin (name):
        global cygwin_resolver #ugh
        if not cygwin_resolver:
            from gub import cygwin
            cygwin_resolver = cygwin.init_dependency_resolver (settings)
        return name_to_dependencies_via_distro (cygwin.get_packages (), name)

    #debian_resolver = None
    def name_to_dependencies_via_debian (name):
        global debian_resolver #ugh
        if not debian_resolver:
            from gub import debian
            debian_resolver = debian.init_dependency_resolver (settings)
        return name_to_dependencies_via_distro (debian.get_packages (), name)

    name_to_dependencies = {
        'cygwin': name_to_dependencies_via_cygwin,
        'debian': name_to_dependencies_via_debian,
        }

    def name_to_dependencies_broker (url):
        platform, x = split_platform (url)
        return name_to_dependencies.get (platform,
                                         name_to_dependencies_via_gub) (url)

    # Must iterate, try:
    #   bin/gub -p tools tar
    #   bin/gub -p tools make tar
    # or
    #   bin/gub darwin-ppc::libtool freebsd-64::libtool
    last_count = len (todo)
    while last_count != len (list (specs.keys ())):
        add = cross.set_cross_dependencies (specs)
        todo += [a for a in add if a not in todo]
        last_count = len (list (specs.keys ()))
        topologically_sorted (todo, {}, name_to_dependencies_broker)

    # Fixup for build from url: specs key is full url, change to
    # base name.  Must use list (dict.keys()), since dict changes during
    # iteration.
    for name in list (specs.keys ()):
        spec = specs[name]
        if name != spec.platform_name ():
            specs[spec.platform_name ()] = spec

    if settings.is_distro:
        def obj_to_dependency_objects (obj):
            return [specs[n] for n in obj.get_platform_build_dependencies ()]
    else:
        def obj_to_dependency_objects (obj):
            return [specs[get_base_package_name (n)]
                    for n in obj.get_platform_build_dependencies ()]

    sorted_specs = topologically_sorted (list (specs.values ()), {},
                                         obj_to_dependency_objects)

    # Make sure we build dependencies in order
    sorted_names = [o.platform_name () for o in sorted_specs]
    return (sorted_names, specs)