Blob Blame History Raw
From c8e25b79e1374f6e5e5046097443d0f395a7c8a8 Mon Sep 17 00:00:00 2001
From: Robin Dunn <robin@alldunn.com>
Date: Tue, 5 Jan 2021 10:56:42 -0800
Subject: [PATCH] Run SIP via its Python interface rather than as a separate
 subprocess

---
 build.py                  |  50 ++--
 buildtools/config.py      |   2 -
 buildtools/wxpysip.py     | 110 +++++++++
 wscript                   |   7 -
 wx/include/wxPython/sip.h | 463 ++++++--------------------------------
 5 files changed, 209 insertions(+), 423 deletions(-)
 create mode 100644 buildtools/wxpysip.py

diff --git a/build.py b/build.py
index 34038cc32..9ffd520b6 100755
--- a/build.py
+++ b/build.py
@@ -45,6 +45,7 @@
                                getVcsRev, runcmd, textfile_open, getSipFiles, \
                                getVisCVersion, getToolsPlatformName, updateLicenseFiles, \
                                TemporaryDirectory
+from buildtools.wxpysip import sip_runner
 
 import buildtools.version as version
 
@@ -1252,11 +1253,30 @@ def cmd_sip(options, args):
         if not newer_group(sipFiles, sbf) and os.path.exists(pycode):
             continue
 
-        pycode = '-X pycode'+base+':'+pycode
-        sip = getSipCmd()
-        cmd = '%s %s -c %s -b %s %s %s'  % \
-            (sip, cfg.SIPOPTS, tmpdir, sbf, pycode, src_name)
-        runcmd(cmd)
+        # Leave it turned off for now. TODO: Experiment with this...
+        # pyi_extract = posixjoin(cfg.PKGDIR, base[1:]) + '.pyi'
+        pyi_extract = None
+
+        # SIP extracts are used to pull python snippets and put them into the
+        # module's .py file
+        pycode = 'pycode'+base+':'+pycode
+
+        sip_runner(src_name,
+            abi_version = '12.8',       # siplib abi version
+            warnings = True,            # enable warning messages
+            docstrings = True,          # enable the automatic generation of docstrings
+            release_gil = True,         # always release and reacquire the GIL
+            sip_module = 'wx.siplib',   # the fully qualified name of the sip module
+            sbf_file=sbf,               # File to write the generated file lists to
+            exceptions = False,         # enable support for exceptions
+            tracing = False,            # generate code with tracing enabled
+            sources_dir = tmpdir,       # the name of the code directory
+            extracts = [pycode],        # add <ID:FILE> to the list of extracts to generate
+            pyi_extract=pyi_extract,    # the name of the .pyi stub file
+            include_dirs = [
+                os.path.join(phoenixDir(), 'src'),
+                os.path.join(phoenixDir(), 'sip', 'gen'),
+            ])
 
         classesNeedingClassInfo = { 'sip_corewxTreeCtrl.cpp' : 'wxTreeCtrl', }
 
diff --git a/buildtools/config.py b/buildtools/config.py
index 99a1a2258..a7c2a0d59 100644
--- a/buildtools/config.py
+++ b/buildtools/config.py
@@ -169,8 +169,6 @@ def finishSetup(self, wx_config=None, debug=None):
                              ('WXUSINGDLL', '1'),
                              ('ISOLATION_AWARE_ENABLED', None),
                              #('NDEBUG',),  # using a 1-tuple makes it do an undef
-                             ('SIP_MODULE_NAME', 'wx.siplib'),
-                             ('SIP_MODULE_BASENAME', 'siplib'),
                              ]
 
             self.libs = []
diff --git a/buildtools/wxpysip.py b/buildtools/wxpysip.py
new file mode 100644
index 000000000..c58102b84
--- /dev/null
+++ b/buildtools/wxpysip.py
@@ -0,0 +1,110 @@
+#----------------------------------------------------------------------
+# Name:        buildtools.wxpysip
+# Purpose:     Code to help migrate to SIP 5 with as little disruption
+#              as possible.
+#
+# Author:      Robin Dunn
+#
+# Created:     4-Jan-2021
+# Copyright:   (c) 2021 by Total Control Software
+# License:     wxWindows License
+#----------------------------------------------------------------------
+
+# NOTE: This code is mostly copied, adapted, and extended from the
+#       sipbuild.legacy.sip5 module. The main intent is to make it easy to run
+#       sip the same way as the legacy sip5 entry point, but without needing to
+#       run a subprocess, and to also add a little missing sip 4 functionality
+#       that we were depending on with the old SIP.
+import os
+
+from sipbuild.code_generator import (set_globals, parse, generateCode,
+        generateExtracts, generateAPI, generateXML, generateTypeHints)
+from sipbuild.exceptions import handle_exception, UserException
+from sipbuild.module import resolve_abi_version
+from sipbuild.version import SIP_VERSION, SIP_VERSION_STR
+
+
+def sip_runner(
+    specification,              # the name of the specification file [default stdin]
+    sources_dir=None,           # the name of the code output directory [default not generated]
+    include_dirs=[],            # add <DIR> to the list of directories to search when importing or including .sip files
+    warnings=False,             # enable warning messages [default disabled]
+    docstrings=False,           # enable the automatic generation of docstrings [default disabled]
+    release_gil=False,          # always release and reacquire the GIL [default only when specified]
+    sip_module=None,            # the fully qualified name of the sip module
+    api_extract=None,           # the name of the QScintilla API file [default not generated
+    exceptions=False,           # enable support for C++ exceptions [default disabled]
+    tracing=False,              # generate code with tracing enabled [default disabled]
+    extracts=[],                # add <ID:FILE> to the list of extracts to generate
+    pyi_extract=None,           # the name of the .pyi stub file [default not generated]
+    sbf_file=None,              # File to write the generated file lists to [default not generated]
+    abi_version=None,           # the sip ABI version
+    backstops=[],               # add <TAG> to the list of timeline backstops
+    py_debug=False,             # generate code for a debug build of Python
+    warnings_are_errors=False,  # warnings are handled as errors
+    parts=0,                    # split the generated code into <FILES> files [default 1 per class]
+    xml_extract=None,           # file to write sip xml to
+    protected_is_public=False,  # enable the protected/public hack [default disabled]
+    source_suffix=None,         # the suffix to use for C or C++ source files [default \".c\" or \".cpp\"]
+    tags=[],                    # add <TAG> to the list of versions/platforms to generate code for
+    disabled_features=[],       # add <FEATURE> to the list of disabled features
+    ):
+
+    print("Running SIP code generator on: {}".format(specification))
+
+    generated_files = []
+    try:
+        # The code generator requires the name of the sip module.
+        if sources_dir is not None and sip_module is None:
+            raise UserException("the name of the sip module must be given")
+
+        # Check the ABI version.
+        abi_major, abi_minor = resolve_abi_version(abi_version).split('.')
+
+        # Set the globals.
+        set_globals(SIP_VERSION, SIP_VERSION_STR, int(abi_major), int(abi_minor),
+                UserException, include_dirs)
+
+        # Parse the input file.
+        pt, _, _, _, tags, disabled_features = parse(specification,
+                (xml_extract is None), tags, backstops, disabled_features,
+                protected_is_public)
+
+        # Generate the bindings.
+        if sources_dir is not None:
+            generated_files = generateCode(pt, sources_dir, source_suffix,
+                    exceptions, tracing, release_gil, parts, tags,
+                    disabled_features, docstrings, py_debug, sip_module)
+
+        if sbf_file is not None:
+            generateBuildFile(sbf_file, generated_files)
+
+        # Generate any extracts.
+        generateExtracts(pt, extracts)
+
+        # Generate the API file.
+        if api_extract is not None:
+            generateAPI(pt, api_extract)
+
+        # Generate the type hints file.
+        if pyi_extract is not None:
+            generateTypeHints(pt, pyi_extract)
+
+        # Generate the XML file.
+        if xml_extract is not None:
+            generateXML(pt, xml_extract)
+
+    except Exception as e:
+        handle_exception(e)
+
+    return generated_files
+
+
+def generateBuildFile(sbf_file, generated_files):
+    header, sources = generated_files
+    header = os.path.basename(header)
+    sources = [os.path.basename(n) for n in sources]
+    with open(sbf_file, 'w') as f:
+        f.write("sources = {}\n".format(' '.join(sources)))
+        f.write("headers = {}\n".format(header))
+
diff --git a/wscript b/wscript
index 1e143be67..7fc4f7ca8 100644
--- a/wscript
+++ b/wscript
@@ -318,13 +318,6 @@ def configure(conf):
         conf.env.CFLAGS_WXPY.append('-UNDEBUG')
         conf.env.CXXFLAGS_WXPY.append('-UNDEBUG')
 
-        # set the name of our siplib module
-        conf.env.CFLAGS_WXPY.append('-DSIP_MODULE_NAME=wx.siplib')
-        conf.env.CXXFLAGS_WXPY.append('-DSIP_MODULE_NAME=wx.siplib')
-
-        conf.env.CFLAGS_WXPY.append('-DSIP_MODULE_BASENAME=siplib')
-        conf.env.CXXFLAGS_WXPY.append('-DSIP_MODULE_BASENAME=siplib')
-
         # Add basic debug info for all builds
         conf.env.CFLAGS_WXPY.append('-g')
         conf.env.CXXFLAGS_WXPY.append('-g')