aboutsummaryrefslogtreecommitdiff
path: root/find-modules/FindPythonModuleGeneration.cmake
blob: 6104c1f41067e8294cb6aa54fd3be70eb448fd90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# SPDX-FileCopyrightText: 2016 Stephen Kelly <steveire@gmail.com>
#
# SPDX-License-Identifier: BSD-3-Clause

#[=======================================================================[.rst:
FindPythonModuleGeneration
--------------------------

This module is experimental and internal.  The interface will likely
change in the coming releases.

Tools and macros for generating python bindings

This will define the following public function:

  ecm_generate_python_binding(TARGET <target>
                              PYTHONNAMESPACE <namespace>
                              MODULENAME <modulename>
                              RULES_FILE <rulesfile>
                              SIP_DEPENDS <dependencies>
                              SIP_INCLUDES <includes>
                              HEADERS <headers>)

Invoking the function will create bindings for the <target> for python 2 and 3,
if available.  The bindings will be put in the namespace <namespace> in python,
and will be available from the module <modulename>.

The optional rules file specifies the rules for creating the bindings

A simple invocation would be:

  ecm_generate_python_binding(TARGET KMyTarget
    PYTHONNAMESPACE PyKF5
    MODULENAME MyTarget
    SIP_DEPENDS QtCore/QtCoremod.sip
    HEADERS ${myTargetHeaders}
  )

which can then be used from python as

  import PyKF5.MyTarget

Inclusion of this module defines the following variables:

``KDE_INSTALL_PYTHON2DIR``, ``KDE_INSTALL_PYTHON3DIR``
    destination for generated bindings
``KDE_INSTALL_FULL_PYTHON2DIR``, ``KDE_INSTALL_FULL_PYTHON3DIR``
    corresponding absolute path

If ``KDE_INSTALL_USE_PYTHON2_SYS_PATHS`` is set to TRUE before including this
module, the default value for ``KDE_INSTALL_PYTHON2DIR`` is instead queried from
pythons distutil.sysconfig.get_python_lib().
If not set, it will default to TRUE if pythons ``sysconfig.PREFIX`` is the same
as ``CMAKE_INSTALL_PREFIX``, otherwise it defaults to FALSE.
This variable should NOT be set from within CMakeLists.txt files, instead it
is intended to be set manually when configuring a project which uses this
module (e.g. by packagers).

Likewise for ``KDE_INSTALL_USE_PYTHON3_SYS_PATHS`` and ``KDE_INSTALL_PYTHON3DIR``.
#]=======================================================================]

macro(_find_python version minor_version)
  set(_CURRENT_VERSION ${version}.${minor_version})
  find_library(GPB_PYTHON${version}_LIBRARY
    NAMES
      python${_CURRENT_VERSION}mu
      python${_CURRENT_VERSION}m
      python${_CURRENT_VERSION}u
      python${_CURRENT_VERSION}
    PATHS
      [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs
      [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs
    # Avoid finding the .dll in the PATH.  We want the .lib.
    NO_SYSTEM_ENVIRONMENT_PATH
  )

  if(GPB_PYTHON${version}_LIBRARY)
    # Use the library's install prefix as a hint
    set(_Python_INCLUDE_PATH_HINT)
    get_filename_component(_Python_PREFIX ${GPB_PYTHON${version}_LIBRARY} PATH)
    get_filename_component(_Python_PREFIX ${_Python_PREFIX} PATH)
    if(_Python_PREFIX)
      set(_Python_INCLUDE_PATH_HINT ${_Python_PREFIX}/include)
    endif()
    unset(_Python_PREFIX)

    find_path(GPB_PYTHON${version}_INCLUDE_DIR
      NAMES Python.h
      HINTS
        ${_Python_INCLUDE_PATH_HINT}
      PATHS
        [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include
        [HKEY_CURRENT_USER\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include
      PATH_SUFFIXES
        python${_CURRENT_VERSION}mu
        python${_CURRENT_VERSION}m
        python${_CURRENT_VERSION}u
        python${_CURRENT_VERSION}
    )
    # sip.h is version agnostic, it may be located in the version specific path
    # or one of the default locations
    find_path(GPB_PYTHON${version}_SIP_DIR
       NAMES sip.h
       PATHS
         ${GPB_PYTHON${version}_INCLUDE_DIR}
    )
  endif()

  find_program(GPB_PYTHON${version}_COMMAND python${version})
endmacro()

macro(_create_imported_python_target version)
  if(GPB_PYTHON${version}_LIBRARY AND GPB_PYTHON${version}_INCLUDE_DIR AND
     EXISTS "${GPB_PYTHON${version}_INCLUDE_DIR}/patchlevel.h" AND
     EXISTS "${GPB_PYTHON${version}_SIP_DIR}/sip.h")
    list(APPEND _pyversions ${version})

    file(STRINGS "${GPB_PYTHON${version}_INCLUDE_DIR}/patchlevel.h" python_version_define
         REGEX "^#define[ \t]+PY_MINOR_VERSION[ \t]+[0-9]+")
    string(REGEX REPLACE "^#define[ \t]+PY_MINOR_VERSION[ \t]+([0-9]+)" "\\1"
                         min_ver "${python_version_define}")
    unset(python_version_define)

    set(pyversion${version}_maj_min ${version}.${min_ver})

    add_library(Python::Libs${version} UNKNOWN IMPORTED)
    set_property(TARGET Python::Libs${version} PROPERTY IMPORTED_LOCATION ${GPB_PYTHON${version}_LIBRARY})
    set_property(TARGET Python::Libs${version} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${GPB_PYTHON${version}_INCLUDE_DIR})

    set(_default_KDE_INSTALL_USE_PYTHON${version}_SYS_PATHS OFF)
    if(NOT DEFINED KDE_INSTALL_USE_PYTHON${version}_SYS_PATHS)
      execute_process (
        COMMAND "${GPB_PYTHON${version}_COMMAND}" -c "import sys; from distutils import sysconfig;sys.stdout.write(sysconfig.PREFIX)"
        OUTPUT_VARIABLE gpb_python${version}_prefix
      )
      if(gpb_python${version}_prefix STREQUAL "${CMAKE_INSTALL_PREFIX}")
        message(STATUS "Installing in the same prefix as Python${version}, adopting their path scheme.")
        set(_default_KDE_INSTALL_USE_PYTHON${version}_SYS_PATHS ON)
      else()
        message(STATUS "NOT installing in the same prefix as Python${version}.")
      endif()
    endif()

  endif()
endmacro()

macro(_report_NOT_FOUND message)
  if(NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
    set(_GPB_MESSAGE_TYPE STATUS)
    if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
      set(_GPB_MESSAGE_TYPE FATAL_ERROR)
    endif()
    message(${_GPB_MESSAGE_TYPE} ${message})
  endif()
  set(PythonModuleGeneration_FOUND FALSE)
  return()
endmacro()

if (NOT Qt5Core_FOUND)
  _report_NOT_FOUND("Qt 5 must be found before finding ${CMAKE_FIND_PACKAGE_NAME}.")
endif()

if (NOT GPB_PYTHON3_LIBRARY)
  set(_PYTHON3_MIN_VERSION 4)

  # This value is safe to increment over time, it is used only as a reasonable
  # upper bound to start searching from
  set(_PYTHON3_MAX_VERSION 50)

  _find_python(3 ${_PYTHON3_MAX_VERSION})

  if (GPB_PYTHON3_LIBRARY)
    message(FATAL_ERROR "The max python version in ${CMAKE_FIND_PACKAGE_NAME} must be updated.")
  endif()

  # Look for the highest supported version of Python 3 by looking for a minor
  # version that doesn't exist and decrementing until we find a match.
  set(_PYTHON3_FIND_VERSION ${_PYTHON3_MAX_VERSION})

  while(NOT GPB_PYTHON3_LIBRARY
      AND NOT GPB_PYTHON3_INCLUDE_DIR
      AND NOT EXISTS "${GPB_PYTHON3_INCLUDE_DIR}/patchlevel.h"
      AND NOT _PYTHON3_FIND_VERSION EQUAL ${_PYTHON3_MIN_VERSION})
    math(EXPR _PYTHON3_FIND_VERSION "${_PYTHON3_FIND_VERSION} - 1")
    _find_python(3 ${_PYTHON3_FIND_VERSION})
  endwhile()
endif()
_create_imported_python_target(3)

_find_python(2 7)
_create_imported_python_target(2)

option (KDE_INSTALL_USE_PYTHON3_SYS_PATHS "Install Python3 bindings to the Python3 install dir"
        "${_default_KDE_INSTALL_USE_PYTHON3_SYS_PATHS}")
option (KDE_INSTALL_USE_PYTHON2_SYS_PATHS "Install Python2 bindings to the Python2 install dir"
        "${_default_KDE_INSTALL_USE_PYTHON2_SYS_PATHS}")

if (NOT _pyversions)
  _report_NOT_FOUND("At least one python version must be available to use ${CMAKE_FIND_PACKAGE_NAME}.")
endif()

foreach(pyversion ${_pyversions})

  if(KDE_INSTALL_PYTHON${pyversion}DIR)
     # Use dir from command line

  elseif(KDE_INSTALL_USE_PYTHON${pyversion}_SYS_PATHS)
    execute_process (
      COMMAND "${GPB_PYTHON${pyversion}_COMMAND}" -c "import sys; from distutils import sysconfig;sys.stdout.write(sysconfig.get_python_lib(plat_specific=True,standard_lib=False))"
      OUTPUT_VARIABLE KDE_INSTALL_PYTHON${pyversion}DIR
    )

  else()
    set(KDE_INSTALL_PYTHON${pyversion}DIR lib/python${pyversion${pyversion}_maj_min}/site-packages)
  endif()

  if(NOT IS_ABSOLUTE ${KDE_INSTALL_PYTHON${pyversion}DIR})
    set(KDE_INSTALL_FULL_PYTHON${pyversion}DIR
        "${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_PYTHON${pyversion}DIR}")
  else()
    set(KDE_INSTALL_FULL_PYTHON${pyversion}DIR "${KDE_INSTALL_PYTHON${pyversion}DIR}")
  endif()

endforeach()

find_program(GBP_SIP_COMMAND sip)

if (NOT GBP_SIP_COMMAND)
  _report_NOT_FOUND("The sip executable must be available to use ${CMAKE_FIND_PACKAGE_NAME}.")
endif()

if (NOT libclang_LIBRARY)
  set(_LIBCLANG_MAX_MAJOR_VERSION 7)
  set(_LIBCLANG_MIN_MAJOR_VERSION 5)

  find_library(libclang_LIBRARY clang-${_LIBCLANG_MAX_MAJOR_VERSION}.0)

  if (libclang_LIBRARY)
    message(FATAL_ERROR "The max libclang version in ${CMAKE_FIND_PACKAGE_NAME} must be updated.")
  endif()

  set(_LIBCLANG_FIND_VERSION ${_LIBCLANG_MAX_MAJOR_VERSION})
  while(NOT libclang_LIBRARY AND NOT _LIBCLANG_FIND_VERSION EQUAL _LIBCLANG_MIN_MAJOR_VERSION)
    math(EXPR _LIBCLANG_FIND_VERSION "${_LIBCLANG_FIND_VERSION} - 1")
    set(_GPB_CLANG_SUFFIX ${_LIBCLANG_FIND_VERSION}.0)
    find_library(libclang_LIBRARY clang-${_LIBCLANG_FIND_VERSION}.0)
  endwhile()

  if (NOT libclang_LIBRARY)
    find_library(libclang_LIBRARY clang)
  endif()
else()
  string(REGEX MATCH ".*clang-([0-9]+\\.[0-9]+).*" _GPB_CLANG_SUFFIX ${libclang_LIBRARY})
  set(_GPB_CLANG_SUFFIX ${CMAKE_MATCH_1})
endif()

if (NOT libclang_LIBRARY)
  _report_NOT_FOUND("Could not find libclang version 5.0 or greater.")
else()
  message(STATUS "Found ${libclang_LIBRARY}")
endif()

foreach (pyversion "2" "3")
  set(GPB_PYTHON_COMMAND ${GPB_PYTHON${pyversion}_COMMAND})
  if (NOT GPB_PYTHON_COMMAND)
    continue()
  endif()
  message(STATUS "Testing if ${GPB_PYTHON_COMMAND} can be used to run sip_generator")

  execute_process(
    COMMAND ${GPB_PYTHON_COMMAND} ${CMAKE_CURRENT_LIST_DIR}/sip_generator.py --self-check ${libclang_LIBRARY}
    RESULT_VARIABLE selfCheckErrors
    ERROR_QUIET
  )

  if (selfCheckErrors)
    message(STATUS "sip_generator self-check for ${GPB_PYTHON_COMMAND} failed")
    unset(GPB_PYTHON_COMMAND)
  else()
    message(STATUS "Self-check passed, Using ${GPB_PYTHON_COMMAND} to generate bindings")
    break()
  endif()
endforeach()

if (NOT GPB_PYTHON_COMMAND)
   _report_NOT_FOUND("No usable python version found to run sip_generator for the ${CMAKE_FIND_PACKAGE_NAME} Module.")
endif()

get_filename_component(libclang_file "${libclang_file}" REALPATH)

find_file(SIP_Qt5Core_Mod_FILE
  NAMES QtCoremod.sip
  HINTS ${KDE_INSTALL_PYTHON3DIR}/PyQt5/bindings/QtCore
  PATH_SUFFIXES share/sip/PyQt5/QtCore
)

if(NOT SIP_Qt5Core_Mod_FILE)
  _report_NOT_FOUND("PyQt module files not found for the ${CMAKE_FIND_PACKAGE_NAME} Module.")
endif()

set(PythonModuleGeneration_FOUND TRUE)

include(CMakeParseArguments)

set(GPB_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})

function(_compute_implicit_include_dirs)
  find_program(_GBP_CLANG_CXX_DRIVER_PATH NAMES clang++-${_GPB_CLANG_SUFFIX} clang++)
  if (NOT _GBP_CLANG_CXX_DRIVER_PATH)
    message(FATAL_ERROR "Failed to find clang driver corresponding to ${libclang_LIBRARY}")
  endif()
  execute_process(COMMAND ${_GBP_CLANG_CXX_DRIVER_PATH} -v -E -x c++ -
                  ERROR_VARIABLE _compilerOutput
                  OUTPUT_VARIABLE _compilerStdout
                  INPUT_FILE /dev/null)

  if( "${_compilerOutput}" MATCHES "> search starts here[^\n]+\n *(.+ *\n) *End of (search) list" )

    # split the output into lines and then remove leading and trailing spaces from each of them:
    string(REGEX MATCHALL "[^\n]+\n" _includeLines "${CMAKE_MATCH_1}")
    foreach(nextLine ${_includeLines})
      # on OSX, gcc says things like this:  "/System/Library/Frameworks (framework directory)", strip the last part
      string(REGEX REPLACE "\\(framework directory\\)" "" nextLineNoFramework "${nextLine}")
      # strip spaces at the beginning and the end
      string(STRIP "${nextLineNoFramework}" _includePath)
      list(APPEND _resultIncludeDirs "${_includePath}")
    endforeach()
  endif()

  set(_GPB_IMPLICIT_INCLUDE_DIRS ${_resultIncludeDirs} PARENT_SCOPE)
endfunction()

function(ecm_generate_python_binding)

    set(oneValueArgs TARGET PYTHONNAMESPACE MODULENAME RULES_FILE)
    set(multiValueArgs SIP_DEPENDS SIP_INCLUDES HEADERS)

    cmake_parse_arguments(GPB "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${GPB_MODULENAME}mod.sip"
          "
%Module ${GPB_PYTHONNAMESPACE}.${GPB_MODULENAME}

%ModuleHeaderCode
#pragma GCC visibility push(default)
%End\n\n")

    set(generator_depends "${GPB_MODULE_DIR}/sip_generator.py"
        "${GPB_MODULE_DIR}/rules_engine.py"
        "${GPB_MODULE_DIR}/FindPythonModuleGeneration.cmake"
        )

    if (NOT _GPB_IMPLICIT_INCLUDE_DIRS)
      _compute_implicit_include_dirs()
    endif()

    foreach(dep ${GPB_SIP_DEPENDS})
        if (IS_ABSOLUTE ${dep})
          list(APPEND generator_depends "${dep}")
        endif()
        file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${GPB_MODULENAME}mod.sip"
          "%Import ${dep}\n\n")
    endforeach()

    set(sip_files)
    set(commands)

    if (NOT GPB_RULES_FILE)
      set(GPB_RULES_FILE "${GPB_MODULE_DIR}/Qt5Ruleset.py")
    endif()

    list(APPEND generator_depends ${GPB_RULES_FILE})

    foreach(hdr ${GPB_HEADERS})

        get_filename_component(hdr_file ${hdr} ABSOLUTE)

        get_filename_component(hdr ${hdr} NAME_WE)
        string(TOLOWER ${hdr}.h hdr_filename)

        if (NOT EXISTS "${hdr_file}")
          message(FATAL_ERROR "File not found: ${hdr_file}")
        endif()

        set(sip_file "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${hdr}.sip")
        list(APPEND sip_files ${sip_file})

        set(inc_dirs "-I$<JOIN:$<TARGET_PROPERTY:${GPB_TARGET},INTERFACE_INCLUDE_DIRECTORIES>,;-I>")
        set(sys_inc_dirs)
        foreach(d ${_GPB_IMPLICIT_INCLUDE_DIRS})
           list(APPEND sys_inc_dirs "-isystem" "${d}")
        endforeach()
        set(comp_defs "-D$<JOIN:$<TARGET_PROPERTY:${GPB_TARGET},INTERFACE_COMPILE_DEFINITIONS>,;-D>")

        # We might like to use $<TARGET_PROPERTY:${GPB_TARGET},CXX_STANDARD>, but
        # unfortunately CMake does not populate that property as a side-effect of evaluating
        # COMPILE_FEATURES (Qt specifies feature requirements in its INTERFACE_COMPILE_FEATURES, and
        # those are consumed to set the CXX_STANDARD internally in CMake, but evidently too late)
        set(stdFlag "-std=gnu++14")

        set(comp_flags "$<JOIN:$<TARGET_PROPERTY:${GPB_TARGET},INTERFACE_COMPILE_OPTIONS>;${stdFlag},;>")

        add_custom_command(OUTPUT ${sip_file}
            COMMAND ${GPB_PYTHON_COMMAND} ${GPB_MODULE_DIR}/sip_generator.py
              --flags " ${inc_dirs};${sys_inc_dirs};${comp_defs};${comp_flags}"
              --include_filename "${hdr_filename}"
              ${libclang_LIBRARY}
              ${GPB_RULES_FILE}
              "${hdr_file}"
              "${sip_file}"
            DEPENDS ${hdr_file} ${generator_depends}
            VERBATIM
        )

        file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${GPB_MODULENAME}mod.sip"
          "%Include ${hdr}.sip\n")
    endforeach()

    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/module.sbf"
        "
target = ${GPB_MODULENAME}
sources = sip${GPB_MODULENAME}cmodule.cpp
headers = sipAPI${GPB_MODULENAME}
"
    )

    get_filename_component(SIP_PYQT5_DIR ${SIP_Qt5Core_Mod_FILE} PATH)
    get_filename_component(SIP_PYQT5_DIR ${SIP_PYQT5_DIR} PATH)

    set(sip_includes -I "${SIP_PYQT5_DIR}")
    if (GPB_SIP_INCLUDES)
      list(APPEND sip_includes -I "$<JOIN:${GPB_SIP_INCLUDES},-I>")
    endif()
    foreach(path ${CMAKE_PREFIX_PATH} ${CMAKE_INSTALL_PREFIX})
      if (EXISTS ${path}/share/sip)
        list(APPEND sip_includes -I "${path}/share/sip")
      endif()
    endforeach()

    add_custom_target(generate_${GPB_MODULENAME}_sip_files ALL DEPENDS ${sip_files})

    add_custom_command(OUTPUT
      "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/unified${GPB_MODULENAME}.cpp"
      COMMAND ${GPB_PYTHON_COMMAND} "${GPB_MODULE_DIR}/run-sip.py" --sip ${GBP_SIP_COMMAND}
       --unify "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/unified${GPB_MODULENAME}.cpp"
       --module-name "${GPB_MODULENAME}"
       -c "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}"
       -b "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/module.sbf"

       -x VendorID -x Py_v3

       -I "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}"
       ${sip_includes}
       "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${GPB_MODULENAME}mod.sip"
       DEPENDS generate_${GPB_MODULENAME}_sip_files "${GPB_MODULE_DIR}/run-sip.py" ${generator_depends}
    )

    add_custom_target(sip_generated_${GPB_MODULENAME}_files ALL
          DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/unified${GPB_MODULENAME}.cpp")

    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}"
         "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}")

    foreach(pyversion ${_pyversions})
        message(STATUS "Found dependencies for python${pyversion}, generating bindings")
        message(STATUS "Python${pyversion} install dir: ${KDE_INSTALL_FULL_PYTHON${pyversion}DIR}")

        execute_process(COMMAND "${CMAKE_COMMAND}"
          "-DPYTHON_UMBRELLA_MODULE_FILE=${CMAKE_BINARY_DIR}/py${pyversion}/${GPB_PYTHONNAMESPACE}/__init__.py"
          -P "${GPB_MODULE_DIR}/GeneratePythonBindingUmbrellaModule.cmake"
        )

        add_library(Py${pyversion}KF5${GPB_MODULENAME} MODULE
          "${CMAKE_CURRENT_BINARY_DIR}/pybuild/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/unified${GPB_MODULENAME}.cpp"
        )
        add_dependencies(Py${pyversion}KF5${GPB_MODULENAME} sip_generated_${GPB_MODULENAME}_files)
        target_link_libraries(Py${pyversion}KF5${GPB_MODULENAME} PRIVATE ${GPB_TARGET} Python::Libs${pyversion})

        target_compile_options(Py${pyversion}KF5${GPB_MODULENAME} PRIVATE -fstack-protector-strong -Wno-deprecated-declarations -Wno-overloaded-virtual)
        target_include_directories(Py${pyversion}KF5${GPB_MODULENAME} PRIVATE ${GPB_SIP_INCLUDES})
        target_link_libraries(Py${pyversion}KF5${GPB_MODULENAME} PRIVATE -Wl,-Bsymbolic-functions -Wl,-z,relro)

        set_property(TARGET Py${pyversion}KF5${GPB_MODULENAME} PROPERTY AUTOMOC OFF)
        set_property(TARGET Py${pyversion}KF5${GPB_MODULENAME} PROPERTY PREFIX "")
        set_property(TARGET Py${pyversion}KF5${GPB_MODULENAME} PROPERTY LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/py${pyversion}/${GPB_PYTHONNAMESPACE}")
        set_property(TARGET Py${pyversion}KF5${GPB_MODULENAME} PROPERTY
            OUTPUT_NAME "${GPB_MODULENAME}")

        if (GPB_SIP_DEPENDS MATCHES PyKF5)
          set(_kf5_python_prefix ${KDE_INSTALL_FULL_PYTHON${pyversion}DIR})
        else()
          set(_kf5_python_prefix ${CMAKE_BINARY_DIR}/py${pyversion})
        endif()
        add_test(NAME Py${pyversion}Test${GPB_MODULENAME} COMMAND
            ${GPB_PYTHON${pyversion}_COMMAND} "${CMAKE_SOURCE_DIR}/autotests/pythontest.py"
            ${_kf5_python_prefix}
        )

        install(DIRECTORY ${CMAKE_BINARY_DIR}/py${pyversion}/${GPB_PYTHONNAMESPACE}
          DESTINATION ${KDE_INSTALL_PYTHON${pyversion}DIR})
        install(FILES ${sip_files} "${CMAKE_CURRENT_BINARY_DIR}/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}/${GPB_MODULENAME}mod.sip"
          DESTINATION share/sip/${GPB_PYTHONNAMESPACE}/${GPB_MODULENAME}
        )
    endforeach()
endfunction()