aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/pythonextensions/pyutil.cpp
blob: 15cb71968f53249d095f8c3f572fe03a82feab8c (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
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Python Extensions Plugin for QtCreator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/

#include "pyutil.h"

#ifdef Q_OS_UNIX
#  include <dlfcn.h> // dlopen
#endif

#include <QtCore/QByteArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QStringList>
#include <QtCore/QTemporaryFile>
#include <QtCore/QDir>

// These are used in python and cause compile-time errors
// if still defined.
#undef signals
#undef slots

#include <sbkpython.h>
#include <sbkconverter.h>
#include <sbkmodule.h>

// Python has no concept of private variables and there
// is no way to declare a namespace or scope that will be
// inaccessible from the user script.
// To avoid naming collisions with the setup and tear down
// scripts that attempt to separate different extensions on
// the level of user code (or at least make them appear separated),
// this macro mangles the names of variables used.
// Use as:
// "... some Python code ..." privatName("my_var_name") "... more code ..."
#define privateName(name) "qt_creator_" name "_symbol_mchawrioklpilnjajqkfl"

// Setup and utility functions for QtCreator bindings
// from typesystem.xml

#if PY_MAJOR_VERSION >= 3
extern "C" PyObject *PyInit_QtCreator();
#else
extern "C" void initQtCreator();
#endif

// These variables store all Python types exported by QtCreators bindings,
// as well as the types exported for QtWidgets.
extern PyTypeObject **SbkPythonExtension_QtCreatorTypes;

// This variable stores the Python module generated by Shiboken
extern PyObject *SbkPythonExtension_QtCreatorModuleObject;

namespace PyUtil {

static State state = PythonUninitialized;

static bool runSimpleString(const std::string &script)
{
    if (PyRun_SimpleString(script.c_str()) == -1) {
        if (PyErr_Occurred())
            PyErr_Print();
        return false;
    }

    return true;
}

static void cleanup()
{
    if (state > PythonUninitialized) {
        Py_Finalize();
        state = PythonUninitialized;
    }
}

State init()
{
    if (state > PythonUninitialized)
        return state;

    // If there is an active python virtual environment, use that environment's packages location.
    QByteArray virtualEnvPath = qgetenv("VIRTUAL_ENV");
    if (!virtualEnvPath.isEmpty())
        qputenv("PYTHONHOME", virtualEnvPath);

    // Python's shared libraries don't work properly if included from other
    // shared libraries. See https://mail.python.org/pipermail/new-bugs-announce/2008-November/003322.html
#ifdef Q_OS_UNIX
    #if PY_MAJOR_VERSION >= 3
    std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+"m.so";
    #else
    std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+".so";
    #endif
    dlopen(version.c_str(), RTLD_LAZY | RTLD_GLOBAL);
#endif

    Py_Initialize();
    qAddPostRoutine(cleanup);
    state = PythonInitialized;

    #if PY_MAJOR_VERSION >= 3
    const bool pythonInitialized = PyInit_QtCreator() != nullptr;
    #else
    const bool pythonInitialized = true;
    initQtCreator();
    #endif
    const bool pyErrorOccurred = PyErr_Occurred() != nullptr;
    if (pythonInitialized && !pyErrorOccurred) {
        state = QtCreatorModuleLoaded;
    } else {
        if (pyErrorOccurred)
            PyErr_Print();
        qWarning("Failed to initialize the QtCreator module.");
    }

    // The Python interpreter eats SIGINT, which is not what we want.
    // This stops it from happening.
    // See https://mail.python.org/pipermail/cplusplus-sig/2012-December/016858.html
    if (!runSimpleString("import signal as " privateName("signal") "\n"
                         "" privateName("signal") ".signal(" privateName("signal") ".SIGINT, " privateName("signal") ".SIG_DFL)")) {
        qWarning("Failed to prevent SIGINT capture.");
    }

    return state;
}

bool createModule(const std::string &moduleName)
{
    if (init() != QtCreatorModuleLoaded)
        return false;

    PyObject *module = PyImport_AddModule(moduleName.c_str());
    if (!module) {
        if (PyErr_Occurred())
            PyErr_Print();
        qWarning() << __FUNCTION__ << "Failed to create module";
        return false;
    }

    return true;
}

bool bindObject(const QString &moduleName, const QString &name, int index, void *o)
{
    if (init() != QtCreatorModuleLoaded)
        return false;

    // Generate the type
    PyTypeObject *typeObject = SbkPythonExtension_QtCreatorTypes[index];

    PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(typeObject), o);
    if (!po) {
        qWarning() << __FUNCTION__ << "Failed to create wrapper for" << o;
        return false;
    }
    Py_INCREF(po);

    return bindPyObject(moduleName, name, po);
}

bool bindShibokenModuleObject(const QString &moduleName, const QString &name)
{
    return bindPyObject(moduleName, name, SbkPythonExtension_QtCreatorModuleObject);
}

bool bindPyObject(const QString &moduleName, const QString &name, void *obj)
{
    if (init() != QtCreatorModuleLoaded)
        return false;

    PyObject *module = PyImport_AddModule(moduleName.toLocal8Bit().constData());
    if (!module) {
        if (PyErr_Occurred())
            PyErr_Print();
        qWarning() << __FUNCTION__ << "Failed to locate module" << moduleName;
        return false;
    }

    if (PyModule_AddObject(module, name.toLocal8Bit().constData(), (PyObject *)obj) < 0) {
        if (PyErr_Occurred())
            PyErr_Print();
        qWarning() << __FUNCTION__ << "Failed to add object" << name << "to" << moduleName;
        return false;
    }

    return true;
}

bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj)
{
    PyObject *moduleDict = PyModule_GetDict((PyObject *)obj);
    if (!moduleDict) {
        if (PyErr_Occurred())
            PyErr_Print();
        qWarning("Could not obtain module dict");
        return false;
    }
    PyObject *moduleItem = PyDict_GetItemString(moduleDict, name.toLocal8Bit().constData());
    if (!moduleDict) {
        if (PyErr_Occurred())
            PyErr_Print();
        qWarning() << "Could not obtain module item" << name;
        return false;
    }
    return bindPyObject(moduleName, name, (void *)moduleItem);
}

bool runScript(const std::string &script)
{
    return (init() >= PythonInitialized && runSimpleString(script));
}

bool runScriptWithPath(const std::string &script, const std::string &path)
{
    // I couldn't find a direct c api, but this should cause no variable name
    // collisions. It also cleans the imported modules after the script finishes
    const std::string s =
"import sys as " privateName("sys") "\n"
"" privateName("path") " = list(" privateName("sys") ".path)\n"
"" privateName("sys") ".path.insert(0, \"" + path + "\")\n"
"" privateName("loaded_modules") " = list(" privateName("sys") ".modules.keys())\n"
"" + script + "\n"
"" privateName("sys") ".path = " privateName("path") "\n"
"for m in list(" privateName("sys") ".modules):\n"
"    if m not in " privateName("loaded_modules") ":\n"
"        del(" privateName("sys") ".modules[m])\n";
    return runScript(s);
}

bool addToSysPath(const std::string &path)
{
    // Add a path to Pythons sys.path
    // Used for installing dependencies into custom
    // directory
    const std::string s =
"import sys as " privateName("sys") "\n"
"" privateName("sys") ".path.append(\"" + path + "\")";
    return runScript(s);
}

bool pipInstallRequirements(const std::string &requirements, const std::string &target)
{
    // Run a requirements.txt file with pip
    const std::string s =
"import pip._internal\n"
"if pip._internal.main(['install', '-t', '" + target + "', '-r', '" + requirements + "']) == 0:\n"
"    open('" + requirements + "'.replace('requirements.txt', 'requirements.txt.installed'), 'a').close()\n";
    return runScriptWithPath(s, "");
}

} // namespace PyUtil