Source

doozer / doozerlib / kitbox / win / __init__.py

from __future__ import with_statement
from doozerlib.job import MultiJob, CallbackJob
from doozerlib import log
from doozerlib.path import mtime
from doozerlib.flagsmap import Always, Append

from os.path import dirname, join, basename, normpath, abspath, exists
import shutil

def _dlls(libs):
    return [normpath(abspath(lib)) for lib in libs if lib.lower().endswith('.dll')]

def augment_shlibjob_to_record_link_info(job, cache, shlibfile, implib, libs):
    linkeddlls = _dlls(libs)

    mj = MultiJob()
    mj.add(job)

    def record_implib_and_dlls():
        with cache.access() as d:
            d['implib'][normpath(abspath(shlibfile))] = implib
            d['linkeddlls'][shlibfile] = linkeddlls

    mj.add(CallbackJob(record_implib_and_dlls))

    return mj


def replace_dlls_with_implibs(cache, libs):
    ret = []
    for obj in libs:
        if obj.lower().endswith('.dll'):
            with cache.access() as d:
                implib = d['implib'].get(normpath(abspath(obj)))

            if implib:
                log.debug('found import library for \'%s\' in cache: \'%s\'' % (obj, implib))
                if exists(implib):
                    ret.append(implib)
                else:
                    log.debug('\'%s\' does not exist' % implib)
                    ret.append(obj)
            else:
                log.debug('no import library for \'%s\' was found in cache' % obj)
                ret.append(obj)
        else:
            ret.append(obj)

    return ret
    

def _find_extra_dlls_for_exe(cache, dlls):
    seen = set(dlls)
    next = set()

    with cache.access() as d:
        while dlls:
            for dll in dlls:
                linked_against_dll = d['linkeddlls'].get(dll) or []
                for lib in linked_against_dll:
                    if not lib in seen: 
                        next.add(lib)
                        seen.add(lib)
            dlls = next
    return seen


def augment_exejob_to_copy_dlls(job, cache, exefile, libs):
    linkeddlls = _dlls(libs)

    mj = MultiJob()
    mj.add(job)

    extra_dlls = _find_extra_dlls_for_exe(cache, linkeddlls)

    def copy_dlls_next_to_exe():
        exedir = dirname(exefile)
        for dll in extra_dlls:
            dest = join(exedir, basename(dll))
            log.msg('copying \'%s\' to \'%s\'' % (dll, dest))
            shutil.copyfile(dll, dest)

    job2 = CallbackJob(copy_dlls_next_to_exe)
    job2.add_to_sig([(dll, mtime(dll)) for dll in extra_dlls])
    mj.add(job2)

    return mj

def ntddi_version():
    return cfgvar(
        'win.ntddi_version', 
        default='0x05010000', # Windows XP, by default
        description='the value of the NTDDI_VERSION macro, determining the minimum target OS'
    )

def target_version():
    ntver = ntddi_version
    if isinstance(ntver, basestring):
        if ntver.upper().startswith('0X'): ntver = int(ntver, 16)
        else: ntver = int(ntver)

    winver = 0x501
    if   0x05000000 <= ntver < 0x05010000: winver = 0x500
    elif 0x05010000 <= ntver < 0x05010200: winver = 0x501
    elif 0x05010200 <= ntver < 0x05020000: winver = 0x502
    elif 0x05020000 <= ntver < 0x05020100: winver = 0x501
    elif 0x05020100 <= ntver < 0x06000000: winver = 0x502
    elif 0x06000000 <= ntver < 0x06010000: winver = 0x600
    elif ntver >= 0x06010000: winver = 0x601

    winver = hex(winver)

    return cfgvar(
        'win.version', 
        default=winver, 
        description='the earliest version of Windows being targeted'
    )

def ie_version():
    winver = target_version()
    if isinstance(winver, basestring):
        if winver.upper().startswith('0X'): winver = int(winver, 16)
        else: winver = int(winver)

    if   winver <= 0x400: iever = 0x500
    elif winver <= 0x500: iever = 0x501
    elif winver <= 0x501: iever = 0x600
    elif winver <  0x600: iever = 0x602
    else: iever = 0x800

    return cfgvar(
        'win.ie_version',
        default=hex(iever),
        description='the value of the _WIN32_IE macro, indicating the minimum version of IE that must be on a target machine'
    ) 

def add_win32_defines_to_flagsmap(flagsmap, target_is_windows_predicate):
    versionmacros = ['WINVER', '_WIN32_WINNT', '_WIN32_WINDOWS']

    class AppendWindowsDefines(object):
        def __call__(self, options):
            # According to Raymond Chen, NTDDI_VERSION should be sufficient but there are reports 
            # that it isn't always, so let's play it safe.
            # http://blogs.msdn.com/b/oldnewthing/archive/2007/04/11/2079137.aspx
            ntddiver = ntddi_version()
            if isinstance(ntddiver, int): ntddiver = hex(ntddiver)
            options.defines.append('NTDDI_VERSION=%s' % ntddiver)

            winver = target_version()
            if isinstance(winver, int): winver = hex(winver)
            options.defines += ['%s=%s' % (d, winver) for d in versionmacros]

            iever = ie_version()
            if isinstance(iever, int): iever = hex(iever)
            options.defines.append('_WIN32_IE=%s' % iever)

            return True

        def __str__(self):
            return 'defines += [' + ', '.join(["'"+m+"=xxx'" for m in versionmacros]) + '], where xxx comes from win.version'

    flagsmap.add(target_is_windows_predicate, AppendWindowsDefines())