aboutsummaryrefslogtreecommitdiffstats
path: root/docs/plugin.md
blob: 9e2bd935782bb880dda719b0f7d21cca63a33b80 (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
# C++ Plugin Documentation

This document is intended for people who want to understand, maintain or
extend the C++ part of this plugin.

**NOTE:** The plugins C++ sources are located at `plugins/pythonextensions`.

## Files
The following files exist as part of the C++ plugin:

 * `pyutil.[h|cpp]`
   - Contains utilities for working with the embedded Python interpreter
   - All the direct interaction with Shiboken / Python happens
     in the .cpp file to prevent a known error
   - These files were originally based of the ['scriptableapplication'](http://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples/scriptableapplication)
     example from the PySide project
 * `pythonextensionsplugin.[h|cpp]`
   - Contains the IPlugin class and manages the life-cycle of Python
     extensions
   - This name is the standard for Qt Creator plugins
 * `pythonextensions_global.h`
   - standard plugin file
 * `typesystem_qtcreator.xml`
   - Typesystem for Shiboken
   - This file describes the classes and types that are bound to
     Python
 * `wrappedclasses.h`
   - This file contains the headers that Shiboken will parse when generating
     the bindings
 * `glue` (directory)
   - This directory contains any glue code needed for the bindings
   - Currently it looks like this will mainly be needed to overcome the missing
     support for C++ function pointers in Shiboken

## Buildsystem
The following is part of the buildsystem. The buildsystem has so far only been
tested on Linux.

 * `pythonextensions.pro`
   - QMake project file
   - The projects dependencies on the Qt Creator side are handled in
     the file `pythonextensions_dependencies.pri`
   - The current build system emerged in part from the standard plugin build
     setup, combined with some .pro code from the 'scriptableapplication' and
     a lot of bits and pices added during the initial development phase (mainly
     through trial and error)
 * `pyside2.pri`
   - This file is originally based of the 'scriptableapplication'
     example from the PySide project
   - It is responsible for detecting the PySide2 configuration
 * `tools/pyside2_config.py`
   - This script is a copy of a script found in the PySide2 examples
   - It detects the PySide2 configuration and is called by the .pri file
   - **Note:** This script will be officially included as a PySide util in the future.

### Goals of the buildsystem
The buildsystem aims to achieve two things

 1. Build the C++ plugin, linking against any relevant libraries
 2. Install the generated shared object, as well as copy the
    `/python` directory into the Qt Creator plugin directory. This
    directory contains the included extension manager, which is implemented
    as a Python extension

## General architecture of the plugin
During initialization, the plugin does several things:

 1. Initialize the Python bindings (this happens during the plugin's `initialize`
    call)
    1. initialize the embedded Python
    2. bind the module object generated by Shiboken
       - Note that this requires a patch for Shiboken, that
         exposes this generated object in a way that allows
         the `PyUtils` functions to access it
    3. bind the PythonExtensionsPlugin instance itself

 2. Run the `setup.py` script for every extension that provides it (this happens
    during the `delayedInitialize` call)
    - This is run here to not block the Qt Creator ui while loading the extensions
    - It is separated from the main extension runtime, to provide a facility to
      extensions that depend on Python modules that are not part of the standard
      Python library (for an example, see `examples/numpysetup` or the extension
      loader)
    - This script fails without an explicit warning in the plugin, however, all
      Python errors are printed to stdout, so they can be inspected there during
      extension development
    - Note that the extension manager attempts to run an extensions `main.py`
      script after installing it, but does not consider the `setup.py` script.
      If `main.py` than fails to run, the extension manager advises the user to
      restart Qt Creator
    - There is currently no facility for scripts to detect if they have loaded
      before their setup was called
    - Because the setup is run every time, the setup script needs to detect if
      e.g. attempting to install dependencies is necessary (e.g. trying to import
      them)

 3. Attempt to run the `main.py` script for every extension (this happens
    during the `delayedInitialize` call)
    - An extension that does not provide a `main.py` is considered to have failed
      loading
    - An extension for which the `main.py` fails to run is considered to have not
      loaded
    - Provided `main.py` succeeds, the plugin is
      considered to have loaded (even if the setup script failed)

The extension manager provides functions to find the extension directory, list
all installed extensions and list all loaded extensions. (The loaded extensions
are tracked by the PythonExtensionPlugin instance.) For details on the signatures
of these functions, see `pythonextensionsplugin.h`.

The plugin directories that are searched for the extension directory (currently
`/python`) are determined using `ExtensionSystem::PluginManager::pluginPaths()`.

## Important points / workarounds for problems regarding the various dependencies

When executing Python code, be sure that Python was initialized (this is typically
done by the provided helper functions). Usually, any extension code should be
executed using `runScriptWithPath()` (never use `runScript()`, as it does not
provide any facilities for separating extensions).

The util function `runScriptWithPath()` runs a supplied script in a way that
allows to import Python packages / modules from the provided path. It also
isolates the script by cleaning the module namespace (and removing all
modifications to sys.path) after the script finishes execution. Currently this is
achieved by wrapping the supplied script with some special Python code, that
implements these precautions. Because of this, no variables which have
`mchawrioklpilnjajqkfl` as part of their name should be used in extension
scripts. This function is used for both executing `setup.py` and `main.py` and
should be used whenever executing user supplied scripts.

The Python initialization needs to manually link the Python shared library due
to a bug in CPython. This is done in `init()` (`PyUtils.cpp`) and has some
facilities for detecting the required version and linking against the correct
shared object. However, this needs to be more extensively tested and will probably
have to be amended with several special cases for different platforms / versions.

In the `PyUtil.cpp` file, the macros `signals` and `slots` are undefined due to
compatibility reasons with the CPython headers. Due to this incompatibility, any
Qt Creator code and Shiboken code needs to be well separated. (These symbols need
to be undefined **BEFORE** including anything Python related.)

The glue code for the MacroExpander functions `registerVariable()` and `registerPrefix()`
stores a reference to a Python object that can be called from other functions of the
MacroExpander. For this to work, there may not be an unmatched call to `PyEval_SaveThread()`
in any of the bindings for these functions. To achieve this, all those functions are modified
using a code injection in the `typesystem_qtcreator.xml`.

### My build suddenly starts seg-faulting for no apparent reason
**DON'T PANIC**, did you change the bindings? Check if the value of
```
SBK_PYTHONEXTENSIONS_INTERNAL_PYTHONEXTENSIONSPLUGIN_IDX
```
changed. (See pyutil.h and the `..._python.h` in the generated bindings.)

## Thinks that could be broken on other platforms

This is _not_ an exhaustive list, _nor_ is it backed by any actual experience with
building this project on non-linux operating systems. However, the following things
are quite hacky and might not work on other systems:

 * Different Python versions might not work
 * Wired Python setups might trip it up (e.g. on macOS)
   - Especially watch out for the manually linked Python in pyutils.cpp
 * The pip install stuff might not select the correct version of Python on every machine