Files
Phoenix/wscript

593 lines
22 KiB
Python

#!/usr/bin/python
#-----------------------------------------------------------------------------
# WAF script for building and installing the wxPython extension modules.
#
# Author: Robin Dunn
# Copyright: (c) 2013 by Total Control Software
# License: wxWindows License
#-----------------------------------------------------------------------------
import sys
import os
import buildtools.config
cfg = buildtools.config.Config(True)
#-----------------------------------------------------------------------------
# Options and configuration
APPNAME = 'wxPython'
VERSION = cfg.VERSION
isWindows = sys.platform.startswith('win')
isDarwin = sys.platform == "darwin"
top = '.'
out = 'build/waf'
def options(opt):
if isWindows:
opt.load('msvc')
else:
opt.load('compiler_cc compiler_cxx')
opt.load('python')
opt.add_option('--debug', dest='debug', action='store_true', default=False,
help='Turn on debug compile options.')
opt.add_option('--python', dest='python', default='', action='store',
help='Full path to the Python executrable to use.')
opt.add_option('--wx_config', dest='wx_config', default='wx-config', action='store',
help='Full path to the wx-config script to be used for this build.')
opt.add_option('--no_magic', dest='no_magic', action='store_true', default=False,
help='Don\'t use linker magic to enable wx libs to be bundled with '
'wxPython. See build.py for more info.')
opt.add_option('--mac_arch', dest='mac_arch', default='', action='store',
help='One or more comma separated architecture names to be used for '
'the Mac builds. Should be at least a subset of the architectures '
'used by wxWidgets and Python')
opt.add_option('--msvc_arch', dest='msvc_arch', default='x86', action='store',
help='The architecture to target for MSVC builds. Supported values '
'are: "x86" or "x64"')
#opt.add_option('--msvc_ver', dest='msvc_ver', default='9.0', action='store',
# help='The MSVC version to use for the build, if multiple versions are '
# 'installed. Currently supported values are: "9.0" or "10.0"')
# TODO: The waf msvc tool has --msvc_version and --msvc_target options
# already. We should just switch to those instead of adding our own
# option names...
def configure(conf):
if isWindows:
# For now simply choose the compiler version based on the Python
# version. We have a chicken-egg problem here. The compiler needs to
# be selected before the Python stuff can be configured, but we need
# Python to know what version of the compiler to use.
# TODO: Fix this
msvc_version = '9.0' #conf.options.msvc_ver
if conf.options.python and '33' in conf.options.python:
msvc_version = '10.0'
conf.env['MSVC_VERSIONS'] = ['msvc ' + msvc_version]
conf.env['MSVC_TARGETS'] = [conf.options.msvc_arch]
conf.load('msvc')
else:
conf.load('compiler_cc compiler_cxx')
if conf.options.python:
conf.env.PYTHON = conf.options.python
conf.load('python')
conf.check_python_version(minver=(2,7,0))
if isWindows:
# Search for the Python headers without doing some stuff that could
# iccorectly fail on Windows. See my_check_python_headers below.
conf.my_check_python_headers()
else:
conf.check_python_headers()
# fetch and save the debug option
conf.env.debug = conf.options.debug
# Ensure that the headers in siplib and Phoenix's src dir can be found
conf.env.INCLUDES_WXPY = ['sip/siplib', 'src']
if isWindows:
# Windows/MSVC specific stuff
cfg.finishSetup(debug=conf.env.debug)
conf.env.INCLUDES_WX = cfg.includes
conf.env.DEFINES_WX = cfg.wafDefines
conf.env.CFLAGS_WX = cfg.cflags
conf.env.CXXFLAGS_WX = cfg.cflags
conf.env.LIBPATH_WX = cfg.libdirs
conf.env.LIB_WX = cfg.libs
conf.env.LIBFLAGS_WX = cfg.lflags
_copyEnvGroup(conf.env, '_WX', '_WXADV')
conf.env.LIB_WXADV += cfg.makeLibName('adv')
_copyEnvGroup(conf.env, '_WX', '_WXSTC')
conf.env.LIB_WXSTC += cfg.makeLibName('stc')
_copyEnvGroup(conf.env, '_WX', '_WXHTML')
conf.env.LIB_WXHTML += cfg.makeLibName('html')
_copyEnvGroup(conf.env, '_WX', '_WXGL')
conf.env.LIB_WXGL += cfg.makeLibName('gl')
_copyEnvGroup(conf.env, '_WX', '_WXWEBVIEW')
conf.env.LIB_WXWEBVIEW += cfg.makeLibName('webview')
_copyEnvGroup(conf.env, '_WX', '_WXXML')
conf.env.LIB_WXXML += cfg.makeLibName('xml', isMSWBase=True)
_copyEnvGroup(conf.env, '_WX', '_WXXRC')
conf.env.LIB_WXXRC += cfg.makeLibName('xrc')
_copyEnvGroup(conf.env, '_WX', '_WXRICHTEXT')
conf.env.LIB_WXRICHTEXT += cfg.makeLibName('richtext')
# ** Add code for new modules here
# tweak the PYEXT compile and link flags if making a --debug build
if conf.env.debug:
for listname in ['CFLAGS_PYEXT', 'CXXFLAGS_PYEXT']:
lst = conf.env[listname]
for opt in '/Ox /MD /DNDEBUG'.split():
try:
lst.remove(opt)
except ValueError:
pass
lst[1:1] = '/Od /MDd /Z7 /D_DEBUG'.split()
conf.env['LINKFLAGS_PYEXT'].append('/DEBUG')
conf.env['LIB_PYEXT'][0] += '_d'
else:
# Configuration stuff for non-Windows ports using wx-config
conf.env.CFLAGS_WX = list()
conf.env.CXXFLAGS_WX = list()
conf.env.CFLAGS_WXPY = list()
conf.env.CXXFLAGS_WXPY = list()
# finish configuring the Config object
conf.env.wx_config = conf.options.wx_config
cfg.finishSetup(conf.env.wx_config, conf.env.debug)
# Check wx-config exists and fetch some values from it
rpath = ' --no-rpath' if not conf.options.no_magic else ''
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs core,net' + rpath,
uselib_store='WX', mandatory=True)
# Run it again with different libs options to get different
# sets of flags stored to use with varous extension modules below.
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs adv,core,net' + rpath,
uselib_store='WXADV', mandatory=True)
libname = '' if cfg.MONOLITHIC else 'stc,' # workaround bug in wx-config
conf.check_cfg(path=conf.options.wx_config, package='',
args=('--cxxflags --libs %score,net' % libname) + rpath,
uselib_store='WXSTC', mandatory=True)
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs html,core,net' + rpath,
uselib_store='WXHTML', mandatory=True)
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs gl,core,net' + rpath,
uselib_store='WXGL', mandatory=True)
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs webview,core,net' + rpath,
uselib_store='WXWEBVIEW', mandatory=True)
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs xml,core,net' + rpath,
uselib_store='WXXML', mandatory=True)
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs xrc,xml,core,net' + rpath,
uselib_store='WXXRC', mandatory=True)
libname = '' if cfg.MONOLITHIC else 'richtext,' # workaround bug in wx-config
conf.check_cfg(path=conf.options.wx_config, package='',
args='--cxxflags --libs %score,net' % libname + rpath,
uselib_store='WXRICHTEXT', mandatory=True)
# ** Add code for new modules here
# NOTE: This assumes that if the platform is not win32 (from
# the test above) and not darwin then we must be using the
# GTK2 port of wxWidgets. If we ever support other ports then
# this code will need to be adjusted.
if not isDarwin:
gtkflags = os.popen('pkg-config gtk+-2.0 --cflags', 'r').read()[:-1]
conf.env.CFLAGS_WX += gtkflags.split()
conf.env.CXXFLAGS_WX += gtkflags.split()
# clear out Python's default NDEBUG and make sure it is undef'd too just in case
if 'NDEBUG' in conf.env.DEFINES_PYEXT:
conf.env.DEFINES_PYEXT.remove('NDEBUG')
conf.env.CFLAGS_WXPY.append('-UNDEBUG')
conf.env.CXXFLAGS_WXPY.append('-UNDEBUG')
# Add basic debug info for all builds
conf.env.CFLAGS_WXPY.append('-g')
conf.env.CXXFLAGS_WXPY.append('-g')
# And if --debug is set turn on more detailed debug info and turn off optimization
if conf.env.debug:
conf.env.CFLAGS_WXPY.extend(['-ggdb', '-O0'])
conf.env.CXXFLAGS_WXPY.extend(['-ggdb', '-O0'])
# Remove some compile flags we don't care about, ones that we may be
# replacing ourselves anyway, or ones which may have duplicates.
flags = ['CFLAGS_PYEXT', 'CXXFLAGS_PYEXT', 'LINKFLAGS_PYEXT',
'CFLAGS_cshlib', 'CXXFLAGS_cshlib', 'LINKFLAGS_cshlib',
'CFLAGS_cxxshlib', 'CXXFLAGS_cxxshlib', 'LINKFLAGS_cxxshlib']
for key in flags:
_cleanFlags(conf, key)
# Use the same compilers that wxWidgets used
if cfg.CC:
conf.env.CC = cfg.CC.split()
if cfg.CXX:
conf.env.CXX = cfg.CXX.split()
# Some Mac-specific stuff
if isDarwin:
conf.env.MACOSX_DEPLOYMENT_TARGET = "10.5"
if conf.options.mac_arch:
conf.env.ARCH_WXPY = conf.options.mac_arch.split(',')
#import pprint
#pprint.pprint( [(k, conf.env[k]) for k in conf.env.keys()] )
#
# This is a copy of WAF's check_python_headers with some problematic stuff ripped out.
#
from waflib.Configure import conf
@conf
def my_check_python_headers(conf):
"""
Check for headers and libraries necessary to extend or embed python by using the module *distutils*.
On success the environment variables xxx_PYEXT and xxx_PYEMBED are added:
* PYEXT: for compiling python extensions
* PYEMBED: for embedding a python interpreter
"""
env = conf.env
if not env['CC_NAME'] and not env['CXX_NAME']:
conf.fatal('load a compiler first (gcc, g++, ..)')
if not env['PYTHON_VERSION']:
conf.check_python_version()
pybin = conf.env.PYTHON
if not pybin:
conf.fatal('Could not find the python executable')
v = 'prefix SO LDFLAGS LIBDIR LIBPL INCLUDEPY Py_ENABLE_SHARED MACOSX_DEPLOYMENT_TARGET LDSHARED CFLAGS'.split()
try:
lst = conf.get_python_variables(["get_config_var('%s') or ''" % x for x in v])
except RuntimeError:
conf.fatal("Python development headers not found (-v for details).")
vals = ['%s = %r' % (x, y) for (x, y) in zip(v, lst)]
conf.to_log("Configuration returned from %r:\n%r\n" % (pybin, '\n'.join(vals)))
dct = dict(zip(v, lst))
x = 'MACOSX_DEPLOYMENT_TARGET'
if dct[x]:
conf.env[x] = conf.environ[x] = dct[x]
env['pyext_PATTERN'] = '%s' + dct['SO'] # not a mistake
# Check for python libraries for embedding
all_flags = dct['LDFLAGS'] + ' ' + dct['CFLAGS']
conf.parse_flags(all_flags, 'PYEMBED')
all_flags = dct['LDFLAGS'] + ' ' + dct['LDSHARED'] + ' ' + dct['CFLAGS']
conf.parse_flags(all_flags, 'PYEXT')
if isWindows:
libname = 'python' + conf.env['PYTHON_VERSION'].replace('.', '')
# TODO: libpath will be incorrect in virtualenv's. Fix this...
libpath = [os.path.join(dct['prefix'], "libs")]
conf.env['LIBPATH_PYEMBED'] = libpath
conf.env.append_value('LIB_PYEMBED', [libname])
conf.env['LIBPATH_PYEXT'] = conf.env['LIBPATH_PYEMBED']
conf.env['LIB_PYEXT'] = conf.env['LIB_PYEMBED']
else:
result = None
for name in ('python' + env['PYTHON_VERSION'], 'python' + env['PYTHON_VERSION'].replace('.', '')):
# LIBPATH_PYEMBED is already set; see if it works.
if not result and env['LIBPATH_PYEMBED']:
path = env['LIBPATH_PYEMBED']
conf.to_log("\n\n# Trying default LIBPATH_PYEMBED: %r\n" % path)
result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBPATH_PYEMBED' % name)
if not result and dct['LIBDIR']:
path = [dct['LIBDIR']]
conf.to_log("\n\n# try again with -L$python_LIBDIR: %r\n" % path)
result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBDIR' % name)
if not result and dct['LIBPL']:
path = [dct['LIBPL']]
conf.to_log("\n\n# try again with -L$python_LIBPL (some systems don't install the python library in $prefix/lib)\n")
result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in python_LIBPL' % name)
if not result:
path = [os.path.join(dct['prefix'], "libs")]
conf.to_log("\n\n# try again with -L$prefix/libs, and pythonXY name rather than pythonX.Y (win32)\n")
result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in $prefix/libs' % name)
if result:
break # do not forget to set LIBPATH_PYEMBED
if result:
env['LIBPATH_PYEMBED'] = path
env.append_value('LIB_PYEMBED', [name])
else:
conf.to_log("\n\n### LIB NOT FOUND\n")
conf.to_log("Include path for Python extensions "
"(found via distutils module): %r\n" % (dct['INCLUDEPY'],))
env['INCLUDES_PYEXT'] = [dct['INCLUDEPY']]
env['INCLUDES_PYEMBED'] = [dct['INCLUDEPY']]
# Code using the Python API needs to be compiled with -fno-strict-aliasing
if env['CC_NAME'] == 'gcc':
env.append_value('CFLAGS_PYEMBED', ['-fno-strict-aliasing'])
env.append_value('CFLAGS_PYEXT', ['-fno-strict-aliasing'])
if env['CXX_NAME'] == 'gcc':
env.append_value('CXXFLAGS_PYEMBED', ['-fno-strict-aliasing'])
env.append_value('CXXFLAGS_PYEXT', ['-fno-strict-aliasing'])
if env.CC_NAME == "msvc":
from distutils.msvccompiler import MSVCCompiler
dist_compiler = MSVCCompiler()
dist_compiler.initialize()
env.append_value('CFLAGS_PYEXT', dist_compiler.compile_options)
env.append_value('CXXFLAGS_PYEXT', dist_compiler.compile_options)
env.append_value('LINKFLAGS_PYEXT', dist_compiler.ldflags_shared)
#-----------------------------------------------------------------------------
# Build command
def build(bld):
# Ensure that the directory containing this script is on the python
# path for spawned commands so the builder and phoenix packages can be
# found.
thisdir = os.path.abspath(".")
sys.path.insert(0, thisdir)
from distutils.file_util import copy_file
from distutils.dir_util import mkpath
from distutils.dep_util import newer, newer_group
from buildtools.config import opj, loadETG, getEtgSipCppFiles
cfg.finishSetup(bld.env.wx_config)
# update the license files
mkpath('license')
for filename in ['preamble.txt', 'licence.txt', 'licendoc.txt', 'lgpl.txt']:
copy_file(opj(cfg.WXDIR, 'docs', filename), opj('license',filename), update=1, verbose=1)
# create the package's __version__ module
open(opj(cfg.PKGDIR, '__version__.py'), 'w').write(
"# This file was generated by Phoenix's wscript.\n\n"
"VERSION_STRING = '%(VERSION)s'\n"
"MAJOR_VERSION = %(VER_MAJOR)s\n"
"MINOR_VERSION = %(VER_MINOR)s\n"
"RELEASE_NUMBER = %(VER_RELEASE)s\n"
"SUBRELEASE_NUMBER = %(VER_SUBREL)s\n\n"
"VERSION = (MAJOR_VERSION, MINOR_VERSION, RELEASE_NUMBER, SUBRELEASE_NUMBER, '%(VER_FLAGS)s')\n"
% cfg.__dict__)
# copy the wx locale message catalogs to the package dir
if isWindows or isDarwin:
cfg.build_locale_dir(opj(cfg.PKGDIR, 'locale'))
# copy __init__.py
copy_file('src/__init__.py', cfg.PKGDIR, update=1, verbose=1)
# Create the build tasks for each of our extension modules.
siplib = bld(
features = 'c cxx cshlib cxxshlib pyext',
target = makeTargetName(bld, 'siplib'),
source = ['sip/siplib/apiversions.c',
'sip/siplib/bool.cpp',
'sip/siplib/descriptors.c',
'sip/siplib/objmap.c',
'sip/siplib/qtlib.c',
'sip/siplib/siplib.c',
'sip/siplib/threads.c',
'sip/siplib/voidptr.c',
],
uselib = 'WX WXPY',
)
makeExtCopyRule(bld, 'siplib')
etg = loadETG('etg/_core.py')
rc = ['src/wxc.rc'] if isWindows else []
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_core'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WX WXPY',
)
makeExtCopyRule(bld, '_core')
etg = loadETG('etg/_adv.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_adv'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXADV WXPY',
)
makeExtCopyRule(bld, '_adv')
etg = loadETG('etg/_dataview.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_dataview'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXADV WXPY', # dataview classes are also in the adv library
)
makeExtCopyRule(bld, '_dataview')
etg = loadETG('etg/_grid.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_grid'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXADV WXPY', # grid classes are also in the adv library
)
makeExtCopyRule(bld, '_grid')
etg = loadETG('etg/_stc.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_stc'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXSTC WXPY',
)
makeExtCopyRule(bld, '_stc')
etg = loadETG('etg/_html.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_html'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXHTML WXPY',
)
makeExtCopyRule(bld, '_html')
etg = loadETG('etg/_glcanvas.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_glcanvas'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXGL WXPY',
)
makeExtCopyRule(bld, '_glcanvas')
etg = loadETG('etg/_html2.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_html2'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXWEBVIEW WXPY',
)
makeExtCopyRule(bld, '_html2')
etg = loadETG('etg/_xml.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_xml'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXXML WXPY',
)
makeExtCopyRule(bld, '_xml')
etg = loadETG('etg/_xrc.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_xrc'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXXRC WXPY',
)
makeExtCopyRule(bld, '_xrc')
etg = loadETG('etg/_richtext.py')
bld(features = 'c cxx cxxshlib pyext',
target = makeTargetName(bld, '_richtext'),
source = getEtgSipCppFiles(etg) + rc,
uselib = 'WXRICHTEXT WXPY',
)
makeExtCopyRule(bld, '_richtext')
# ** Add code for new modules here
#-----------------------------------------------------------------------------
# helpers
# Remove some unwanted flags from the given key in the context's environment
def _cleanFlags(ctx, key):
cleaned = list()
skipnext = False
for idx, flag in enumerate(ctx.env[key]):
if flag in ['-arch', '-isysroot', '-compatibility_version', '-current_version']:
skipnext = True # implicitly skips this one too
elif not skipnext:
cleaned.append(flag)
else:
skipnext = False
ctx.env[key] = cleaned
def makeTargetName(bld, name):
if isWindows and bld.env.debug:
name += '_d'
return name
# Make a rule that will copy a built extension module to the in-place package
# dir so we can test locally without doing an install.
def makeExtCopyRule(bld, name):
name = makeTargetName(bld, name)
src = bld.env.pyext_PATTERN % name
tgt = 'pkg.%s' % name # just a name to be touched to serve as a timestamp of the copy
bld(rule=copyFileToPkg, source=src, target=tgt, after=name)
# This is the task function to be called by the above rule.
def copyFileToPkg(task):
from distutils.file_util import copy_file
from buildtools.config import opj
src = task.inputs[0].abspath()
tgt = task.outputs[0].abspath()
task.exec_command('touch %s' % tgt)
tgt = opj(cfg.PKGDIR, os.path.basename(src))
copy_file(src, tgt, verbose=1)
return 0
# Copy all the items in env with a matching postfix to a similarly
# named item with the dest postfix.
def _copyEnvGroup(env, srcPostfix, destPostfix):
import copy
for key in env.keys():
if key.endswith(srcPostfix):
newKey = key[:-len(srcPostfix)] + destPostfix
env[newKey] = copy.copy(env[key])
#-----------------------------------------------------------------------------