From 6b99ffa7ce711a9aaad74b718bca8d9e3276bb9f Mon Sep 17 00:00:00 2001 From: Martin Rodriguez Reboredo Date: Sat, 22 Mar 2025 16:25:55 -0300 Subject: [PATCH 1/2] Fix Qt compilation errors --- etg/listctrl.py | 2 +- src/core_ex.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/etg/listctrl.py b/etg/listctrl.py index 2c76b003c..c40233a85 100644 --- a/etg/listctrl.py +++ b/etg/listctrl.py @@ -325,7 +325,7 @@ def run(): c.addCppMethod('wxWindow*', 'GetMainWindow', '()', body="""\ - #if defined(__WXMSW__) || defined(__WXMAC__) + #if defined(__WXMSW__) || defined(__WXMAC__) || defined(__WXQT__) return self; #else return (wxWindow*)self->m_mainWin; diff --git a/src/core_ex.cpp b/src/core_ex.cpp index f39b28296..544dac0e9 100644 --- a/src/core_ex.cpp +++ b/src/core_ex.cpp @@ -120,6 +120,10 @@ void wxPyCoreModuleInject(PyObject* moduleDict) #define wxPort "__WXGTK__" #define wxPortName "wxGTK" #endif +#ifdef __WXQT__ +#define wxPort "__WXQT__" +#define wxPortName "wxQT" +#endif #ifdef __WXMSW__ #define wxPort "__WXMSW__" #define wxPortName "wxMSW" @@ -188,6 +192,9 @@ void wxPyCoreModuleInject(PyObject* moduleDict) _AddInfoString("gtk1"); #endif #endif +#ifdef __WXQT__ + _AddInfoString("qt"); +#endif #ifdef __WXDEBUG__ _AddInfoString("wx-assertions-on"); #else From 544b779247828012c2d9df11550b89793d592e21 Mon Sep 17 00:00:00 2001 From: Martin Rodriguez Reboredo Date: Sat, 22 Mar 2025 16:23:56 -0300 Subject: [PATCH 2/2] Multi platform backends for Linux --- build.py | 19 ++++++++++++ src/__init__.py | 10 +++++++ src/multi.py | 16 ++++++++++ wscript | 80 +++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/multi.py diff --git a/build.py b/build.py index 5dd0e09a3..70097e132 100755 --- a/build.py +++ b/build.py @@ -420,6 +420,7 @@ def makeOptionParser(): ("keep_hash_lines",(False, "Don't remove the '#line N' lines from the SIP generated code")), ("gtk2", (False, "On Linux build for gtk2 (default gtk3)")), ("gtk3", (True, "On Linux build for gtk3")), + ("qt", (False, "On Linux build for qt")), ("osx_cocoa", (True, "Build the OSX Cocoa port on Mac (default)")), ("osx_carbon", (False, "Build the OSX Carbon port on Mac (unsupported)")), ("mac_framework", (False, "Build wxWidgets as a Mac framework.")), @@ -1746,6 +1747,7 @@ def _getWxCompiler(flag, compName, flagName): wafBuildDir = posixjoin(wafBuildBase, 'release') build_options = list() + multi_options = list() if options.verbose: build_options.append('--verbose') @@ -1768,8 +1770,15 @@ def _getWxCompiler(flag, compName, flagName): build_options.append('--gtk2') wafBuildDir = posixjoin(wafBuildBase, 'gtk2') if options.gtk3: + if options.qt: + # Build qt later + build_options.append('--multi') + multi_options = build_options[:] build_options.append('--gtk3') wafBuildDir = posixjoin(wafBuildBase, 'gtk3') + if options.qt and not options.gtk3: + build_options.append('--qt') + wafBuildDir = posixjoin(wafBuildBase, 'qt') build_options.append('--python="%s"' % PYTHON) build_options.append('--out=%s' % wafBuildDir) # this needs to be the last option @@ -1802,6 +1811,16 @@ def _onWafError(): msg('*-'*40) runcmd(cmd, onError=_onWafError) + if not isWindows and not isDarwin and options.gtk3 and options.qt: + multi_options = [f'{opt}-qt' if 'wx-config' in opt else opt for opt in multi_options] + multi_options.append('--qt') + wafBuildDir = posixjoin(wafBuildBase, 'qt') + multi_options.append('--python="%s"' % PYTHON) + multi_options.append('--out=%s' % wafBuildDir) + + cmd = '"%s" %s %s configure build %s' % (PYTHON, waf, ' '.join(multi_options), options.extra_waf) + runcmd(cmd, onError=_onWafError) + if isWindows and options.both: build_options.remove('--debug') del build_options[-1] diff --git a/src/__init__.py b/src/__init__.py index 649af3a7b..9259e2fe8 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -11,6 +11,16 @@ import wx.__version__ __version__ = wx.__version__.VERSION_STRING +import os + + +# Select platform backend +__plat__ = '@DEFAULT@' +if 'WX_API' in os.environ: + __plat__ = os.environ['WX_API'] + if __plat__ not in ['@PLATS@']: + raise ImportError(f'invalid WX_API {__plat__}') + # Import all items from the core wxPython module so they appear in the wx # package namespace. diff --git a/src/multi.py b/src/multi.py new file mode 100644 index 000000000..764a5b46d --- /dev/null +++ b/src/multi.py @@ -0,0 +1,16 @@ +import importlib +import sys + +root = importlib.import_module(__package__) + +_@NAME@ = importlib.import_module(f'.@NAME@_{root.__plat__}', __package__) + +if hasattr(_@NAME@, '__all__'): + __all__ = _@NAME@.__all__ +else: + __all__ = [name for name in dir(_@NAME@) if name != "__name__"] + +globals().update({name: getattr(_@NAME@, name) for name in __all__}) + +del root +del _@NAME@ diff --git a/wscript b/wscript index e4f982e40..53df04071 100644 --- a/wscript +++ b/wscript @@ -52,6 +52,10 @@ def options(opt): help='On Linux build for gtk2 (default gtk3)') opt.add_option('--gtk3', dest='gtk3', action='store_true', default=True, help='On Linux build for gtk3') + opt.add_option('--qt', dest='qt', action='store_true', default=False, + help='On Linux build for qt') + opt.add_option('--multi', dest='multi', action='store_true', default=False, + help='On Linux build for multiple backends') opt.add_option('--no_msvc', dest='use_msvc', action='store_false', default=isWindows, help='Set to use a MinGW toolchain') opt.add_option('--msvc_arch', dest='msvc_arch', default='x86', action='store', @@ -283,15 +287,27 @@ def configure(conf): # wxWidgets. If we ever support other ports then this code will need # to be adjusted. if not isDarwin: + platflags = '' if conf.options.gtk2: conf.options.gtk3 = False + conf.options.qt = False + conf.options.multi = False + if conf.options.qt: + conf.options.gtk3 = False if conf.options.gtk2: - gtkflags = os.popen('pkg-config gtk+-2.0 --cflags', 'r').read()[:-1] - else: - gtkflags = os.popen('pkg-config gtk+-3.0 --cflags', 'r').read()[:-1] + platflags = os.popen('pkg-config gtk+-2.0 --cflags', 'r').read()[:-1] + elif conf.options.gtk3: + platflags = os.popen('pkg-config gtk+-3.0 --cflags', 'r').read()[:-1] + elif conf.options.qt: + platflags = os.popen('pkg-config Qt5Core Qt5Gui --cflags', 'r').read()[:-1] + conf.options.gtk3 = False + + conf.env.gtk3 = conf.options.gtk3 + conf.env.qt = conf.options.qt + conf.env.multi = conf.options.multi - conf.env.CFLAGS_WX += gtkflags.split() - conf.env.CXXFLAGS_WX += gtkflags.split() + conf.env.CFLAGS_WX += platflags.split() + conf.env.CXXFLAGS_WX += platflags.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: @@ -534,14 +550,31 @@ def build(bld): cfg.build_locale_dir(opj(cfg.PKGDIR, 'locale')) # copy .py files that need to go into the root wx package dir - for name in ['src/__init__.py', 'src/gizmos.py',]: - copy_file(name, cfg.PKGDIR, update=1, verbose=1) + if isWindows: + default = 'msw' + elif isDarwin: + default = 'mac' + elif bld.env.gtk2: + default = 'gtk2' + elif bld.env.gtk3: + default = 'gtk3' + elif bld.env.qt and not bld.env.multi: + default = 'qt' + plats = ['gtk3', 'qt'] if bld.env.multi else [default] + bld(features='subst', source='src/__init__.py', target='__init__.py', DEFAULT=default, PLATS=str(plats)) + bld(rule=copyFileToPkg, source='__init__.py', target='pkg.__init__') + copy_file('src/gizmos.py', cfg.PKGDIR, update=1, verbose=1) + + extra = dict() + plat = '' + if not isWindows and not isDarwin and bld.env.multi: + plat = '_gtk3' if bld.env.gtk3 else '_qt' # Create the build tasks for each of our extension modules. - addRelwithdebugFlags(bld, 'siplib') + addRelwithdebugFlags(bld, f'siplib{plat}') siplib = bld( features = 'c cxx cshlib cxxshlib pyext', - target = makeTargetName(bld, 'siplib'), + target = makeTargetName(bld, f'siplib{plat}'), source = ['sip/siplib/apiversions.c', 'sip/siplib/descriptors.c', 'sip/siplib/int_convertors.c', @@ -553,8 +586,12 @@ def build(bld): 'sip/siplib/voidptr.c', ], uselib = 'siplib WX WXPY', + **extra, ) - makeExtCopyRule(bld, 'siplib') + makeExtCopyRule(bld, f'siplib{plat}') + if not isWindows and not isDarwin and bld.env.multi: + siplib.env['LDFLAGS'].append_value(f'--defsym=PyInit_siplib{plat}=PyInit_siplib') + makeModSubstRule(bld, 'siplib') # Add build rules for each of our ETG generated extension modules makeETGRule(bld, 'etg/_core.py', '_core', 'WX') @@ -613,6 +650,17 @@ def makeExtCopyRule(bld, name): bld(rule=copyFileToPkg, source=src, target=tgt, after=name) +# Make a rule that will copy a multi module to the in-place package +# dir so we can test locally without doing an install. +def makeModSubstRule(bld, name): + name = makeTargetName(bld, name) + subst = f'{name}.py' + # just a name to be touched to serve as the timestamp of the copy + tgt = 'pkg.%s' % os.path.splitext(subst)[0] + bld(features='subst', source='src/multi.py', target=subst, NAME=name) + bld(rule=copyFileToPkg, source=subst, target=tgt) + + # This is the task function to be called by the above rule. def copyFileToPkg(task): from distutils.file_util import copy_file @@ -653,6 +701,11 @@ def _copyEnvGroup(env, srcPostfix, destPostfix): def makeETGRule(bld, etgScript, moduleName, libFlags): from buildtools.config import loadETG, getEtgSipCppFiles + extra = dict() + if not isWindows and not isDarwin and bld.env.multi: + base = moduleName + moduleName += '_gtk3' if bld.env.gtk3 else '_qt' + addRelwithdebugFlags(bld, moduleName) rc = [] @@ -666,12 +719,15 @@ def makeETGRule(bld, etgScript, moduleName, libFlags): rc = [rc_name] etg = loadETG(etgScript) - bld(features='c cxx cxxshlib pyext', + mod = bld(features='c cxx cxxshlib pyext', target=makeTargetName(bld, moduleName), source=getEtgSipCppFiles(etg) + rc, uselib='{} {} WXPY'.format(moduleName, libFlags), - ) + **extra) makeExtCopyRule(bld, moduleName) + if not isWindows and not isDarwin and bld.env.multi: + mod.env['LDFLAGS'].append_value(f'--defsym=PyInit_{moduleName}=PyInit_{base}') + makeModSubstRule(bld, base) # Add flags to create .pdb files for debugging with MSVC