Remove Jupyter Integration (#1398)

* Remove Jupyter Integration

Replaced by https://github.com/radareorg/cutter-jupyter

* Remove duplicate vars in .appveyor.yml
This commit is contained in:
Florian Märkl 2019-03-25 21:43:00 +01:00 committed by GitHub
parent eac91ed9c8
commit 1710829267
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 11 additions and 1106 deletions

View File

@ -49,8 +49,8 @@ before_build:
# Build config
build_script:
- cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=false CUTTER_ENABLE_JUPYTER=true CUTTER_ENABLE_QTWEBENGINE=false CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.cp36-win_amd64.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.cp36-win_amd64.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems")
- cmd: if defined MESON ( python meson.py --release --dist=%ARTIFACT_PATH% --backend=%BACKEND% --python --jupyter )
- cmd: if defined QMAKE ( call prepare_r2.bat && call build.bat CUTTER_APPVEYOR_R2DEC=true CUTTER_ENABLE_PYTHON=true CUTTER_ENABLE_PYTHON_BINDINGS=true SHIBOKEN_EXECUTABLE="%CUTTER_DEPS_DIR%\pyside\bin\shiboken2.exe" SHIBOKEN_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/shiboken2" SHIBOKEN_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/shiboken2.cp36-win_amd64.lib" PYSIDE_INCLUDEDIR="%CUTTER_DEPS_DIR%/pyside/include/PySide2" PYSIDE_LIBRARY="%CUTTER_DEPS_DIR%/pyside/lib/pyside2.cp36-win_amd64.lib" PYSIDE_TYPESYSTEMS="%CUTTER_DEPS_DIR%/pyside/share/PySide2/typesystems")
- cmd: if defined MESON ( python meson.py --release --dist=%ARTIFACT_PATH% --backend=%BACKEND% --python )
after_build:
- cmd: if defined QMAKE ( set "PATH=%CD%\r2_dist_%ARCH%;%PATH%" && powershell scripts\bundle_r2dec.ps1 "%CD%\%ARTIFACT_PATH%" )

View File

@ -68,7 +68,6 @@ addons:
install:
- scripts/fetch_deps.sh
- source cutter-deps/env.sh
- python3 -m pip install -r scripts/pip_requirements.txt
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/llvm/bin:$PATH; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export LD_LIBRARY_PATH="`llvm-config --libdir`:$LD_LIBRARY_PATH"; fi
@ -90,8 +89,6 @@ script:
qmake
CUTTER_ENABLE_PYTHON=true
CUTTER_ENABLE_PYTHON_BINDINGS=true
CUTTER_ENABLE_JUPYTER=true
CUTTER_ENABLE_QTWEBENGINE=false
PREFIX=/usr
APPIMAGE=1
../src &&
@ -104,8 +101,6 @@ script:
-DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3"
-DCUTTER_ENABLE_PYTHON=ON
-DCUTTER_ENABLE_PYTHON_BINDINGS=ON
-DCUTTER_ENABLE_JUPYTER=ON
-DCUTTER_ENABLE_QTWEBENGINE=OFF
../src &&
make -j4;
fi
@ -114,8 +109,6 @@ script:
qmake
CUTTER_ENABLE_PYTHON=true
CUTTER_ENABLE_PYTHON_BINDINGS=true
CUTTER_ENABLE_JUPYTER=true
CUTTER_ENABLE_QTWEBENGINE=false
CUTTER_BUNDLE_R2_APPBUNDLE=true
PYTHON_FRAMEWORK_DIR=$CUTTER_DEPS_PYTHON_FRAMEWORK_DIR
../src &&
@ -128,8 +121,6 @@ script:
-DPYTHON_EXECUTABLE="$CUTTER_DEPS_PYTHON_PREFIX/bin/python3"
-DCUTTER_ENABLE_PYTHON=ON
-DCUTTER_ENABLE_PYTHON_BINDINGS=ON
-DCUTTER_ENABLE_JUPYTER=ON
-DCUTTER_ENABLE_QTWEBENGINE=OFF
../src &&
make -j4;
fi

View File

@ -9,7 +9,6 @@ ERR=0
BUILD="build"
QMAKE_CONF=""
ROOT_DIR=`pwd`
#QMAKE_CONF="CUTTER_ENABLE_JUPYTER=false CUTTER_ENABLE_QTWEBENGINE=false"
# Create translations
lrelease ./src/Cutter.pro

View File

@ -24,8 +24,8 @@ Building options
----------------
Note that there are two major building options available:
- ``CUTTER_ENABLE_JUPYTER`` is used to compile Cutter with bundled Python and Jupyter module
- ``CUTTER_ENABLE_QTWEBENGINE`` is used to compile Cutter with bundled QtWebEngine (to ease jupyter console usage)
- ``CUTTER_ENABLE_PYTHON`` compile with Python support
- ``CUTTER_ENABLE_PYTHON_BINDINGS`` automatically generate Python Bindings with Shiboken2, required for Python plugins!
--------------
@ -124,24 +124,6 @@ containing bin/, lib/, include/, etc.) and specify it to CMake using
rm CMakeCache.txt # the cache may be polluted with unwanted libraries found before
cmake -DCMAKE_PREFIX_PATH=/opt/Qt/5.9.1/gcc_64 ..
..
``ModuleNotFoundError`` upon starting Cutter.
This can be resolved by either: 1. Disabling the optional jupyter
support during building by modifying ``build.sh`` as follows:
- Uncomment
``#QMAKE_CONF="CUTTER_ENABLE_JUPYTER=false CUTTER_ENABLE_QTWEBENGINE=false"``
- Comment out the prior empty ``QMAKE_CONF=""``
2. Or alternatively by installing the two python dependencies manually
afterwards via:
::
pip3 install notebook jupyter_client
Building with Meson (Windows)
-----------------------------

View File

@ -48,9 +48,7 @@ def build(args):
cutter_builddir = os.path.join(ROOT, args.dir)
if not os.path.exists(cutter_builddir):
defines = ['-Denable_python=%s' % str(args.python).lower(),
'-Denable_python_bindings=%s' % str(args.python_bindings).lower(),
'-Denable_jupyter=%s' % str(args.jupyter).lower(),
'-Denable_webengine=%s' % str(args.webengine).lower()]
'-Denable_python_bindings=%s' % str(args.python_bindings).lower()]
if os.name == 'nt':
defines.append('-Dradare2:r2_incdir=radare2/include')
defines.append('-Dradare2:r2_libdir=radare2/lib')
@ -79,10 +77,6 @@ def main():
help='Enable Python support')
parser.add_argument('--python-bindings', action='store_true',
help='Enable Python Bindings')
parser.add_argument('--jupyter', action='store_true',
help='Enable Jupyter support')
parser.add_argument('--webengine', action='store_true',
help='Enable QtWebEngine support')
parser.add_argument('--release', action='store_true',
help='Set the build as Release (remove debug info)')
parser.add_argument('--nobuild', action='store_true',

View File

@ -12,5 +12,4 @@ Copy-Item .\python_embed\${py_base}.zip -Destination $dist\$py_base
Copy-Item .\python_embed\*.pyd -Destination $dist\$py_base
Copy-Item .\python_embed\sqlite3.dll -Destination $dist\$py_base
Copy-Item .\python_embed\python*.dll -Destination $dist
& python -m pip install -I --no-compile -t "${dist}\${py_base}\site-packages" jupyter ipykernel==4.8.2 jsonschema==2.6.0 pyzmq==17.1.2 notebook==5.6.0 tornado==5.1.1
[System.IO.File]::WriteAllLines("${dist}\${py_base}._pth", "${py_base}`r`n${py_base}\${py_base}.zip`r`n${py_base}\site-packages")

View File

@ -1,3 +0,0 @@
jupyter
ipykernel==4.10.0
pyzmq==17.1.2

View File

@ -10,8 +10,6 @@ set(CUTTER_PYTHON_MIN 3.5)
option(CUTTER_ENABLE_PYTHON "Enable Python integration. Requires Python >= ${CUTTER_PYTHON_MIN}." OFF)
option(CUTTER_ENABLE_PYTHON_BINDINGS "Enable generating Python bindings with Shiboken2. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
option(CUTTER_ENABLE_JUPYTER "Enable Jupyter integration. Unused if CUTTER_ENABLE_PYTHON=OFF." OFF)
option(CUTTER_ENABLE_QTWEBENGINE "Use QtWebEngine for in-app Jupyter Browser. Unused if CUTTER_ENABLE_JUPYTER=OFF." OFF)
if(NOT CUTTER_ENABLE_PYTHON)
set(CUTTER_ENABLE_PYTHON_BINDINGS OFF)
@ -45,14 +43,6 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Svg Network)
if(CUTTER_ENABLE_JUPYTER AND CUTTER_ENABLE_QTWEBENGINE)
find_package(Qt5 COMPONENTS WebEngineWidgets)
if(NOT Qt5_FOUND)
message(FATAL_ERROR "QtWebEngine could not be found which is required for the in-app Jupyter Browser.
If you do not want to enable this in-app Browser, re-run CMake with -DCUTTER_ENABLE_QTWEBENGINE=OFF.")
endif()
add_definitions(-DCUTTER_ENABLE_QTWEBENGINE)
endif()
if(WIN32)
@ -80,10 +70,6 @@ if(CUTTER_ENABLE_PYTHON)
include_directories(${PYTHON_INCLUDE_DIRS})
add_definitions(-DCUTTER_ENABLE_PYTHON)
if(CUTTER_ENABLE_JUPYTER)
add_definitions(-DCUTTER_ENABLE_JUPYTER)
endif()
if(CUTTER_ENABLE_PYTHON_BINDINGS)
find_package(PythonInterp REQUIRED)
find_package(Shiboken2 "${Qt5_VERSION}" REQUIRED)
@ -107,8 +93,6 @@ message(STATUS "Building Cutter version ${CUTTER_VERSION_FULL}")
message(STATUS "Options:")
message(STATUS "- Python: ${CUTTER_ENABLE_PYTHON}")
message(STATUS "- Python Bindings: ${CUTTER_ENABLE_PYTHON_BINDINGS}")
message(STATUS "- Jupyter: ${CUTTER_ENABLE_JUPYTER}")
message(STATUS "- QtWebEngine: ${CUTTER_ENABLE_QTWEBENGINE}")
message(STATUS "")
@ -172,7 +156,4 @@ if(CUTTER_ENABLE_PYTHON)
endif()
endif()
if(CUTTER_ENABLE_PYTHON AND CUTTER_ENABLE_JUPYTER AND CUTTER_ENABLE_QTWEBENGINE)
target_link_libraries(Cutter Qt5::WebEngineWidgets)
endif()

View File

@ -43,16 +43,6 @@ equals(CUTTER_ENABLE_PYTHON, true) {
}
}
!defined(CUTTER_ENABLE_JUPYTER, var) CUTTER_ENABLE_JUPYTER=false
equals(CUTTER_ENABLE_PYTHON, true) {
equals(CUTTER_ENABLE_JUPYTER, true) CONFIG += CUTTER_ENABLE_JUPYTER
}
!defined(CUTTER_ENABLE_QTWEBENGINE, var) CUTTER_ENABLE_QTWEBENGINE=false
equals(CUTTER_ENABLE_JUPYTER, true) {
equals(CUTTER_ENABLE_QTWEBENGINE, true) CONFIG += CUTTER_ENABLE_QTWEBENGINE
}
!defined(CUTTER_BUNDLE_R2_APPBUNDLE, var) CUTTER_BUNDLE_R2_APPBUNDLE=false
equals(CUTTER_BUNDLE_R2_APPBUNDLE, true) CONFIG += CUTTER_BUNDLE_R2_APPBUNDLE
@ -75,21 +65,6 @@ CUTTER_ENABLE_PYTHON_BINDINGS {
message("Python Bindings disabled. (requires CUTTER_ENABLE_PYTHON=true)")
}
CUTTER_ENABLE_JUPYTER {
message("Jupyter support enabled.")
DEFINES += CUTTER_ENABLE_JUPYTER
} else {
message("Jupyter support disabled. (requires CUTTER_ENABLE_PYTHON=true)")
}
CUTTER_ENABLE_QTWEBENGINE {
message("QtWebEngine support enabled.")
DEFINES += CUTTER_ENABLE_QTWEBENGINE
QT += webenginewidgets
} else {
message("QtWebEngine support disabled. (requires CUTTER_ENABLE_JUPYTER=true)")
}
INCLUDEPATH *= . core widgets dialogs common plugins
win32 {
@ -290,10 +265,7 @@ SOURCES += \
widgets/HeadersWidget.cpp \
widgets/SearchWidget.cpp \
CutterApplication.cpp \
common/JupyterConnection.cpp \
widgets/JupyterWidget.cpp \
common/PythonAPI.cpp \
common/NestedIPyKernel.cpp \
dialogs/R2PluginsDialog.cpp \
widgets/CutterDockWidget.cpp \
widgets/CutterTreeWidget.cpp \
@ -404,10 +376,7 @@ HEADERS += \
widgets/TypesWidget.h \
widgets/HeadersWidget.h \
widgets/SearchWidget.h \
common/JupyterConnection.h \
widgets/JupyterWidget.h \
common/PythonAPI.h \
common/NestedIPyKernel.h \
dialogs/R2PluginsDialog.h \
widgets/CutterDockWidget.h \
widgets/CutterTreeWidget.h \
@ -496,7 +465,6 @@ FORMS += \
widgets/TypesWidget.ui \
widgets/HeadersWidget.ui \
widgets/SearchWidget.ui \
widgets/JupyterWidget.ui \
dialogs/R2PluginsDialog.ui \
dialogs/VersionInfoDialog.ui \
widgets/ZignaturesWidget.ui \

View File

@ -1,8 +1,5 @@
#include "common/PythonManager.h"
#include "CutterApplication.h"
#ifdef CUTTER_ENABLE_JUPYTER
#include "common/JupyterConnection.h"
#endif
#include "plugins/PluginManager.h"
#include "CutterConfig.h"

View File

@ -34,6 +34,8 @@ int main(int argc, char *argv[])
settings.setValue("settings_migrated", true);
}
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // needed for QtWebEngine inside Plugins
CutterApplication a(argc, argv);
if (Config()->getAutoUpdateEnabled()) {

View File

@ -1,148 +0,0 @@
#ifdef CUTTER_ENABLE_JUPYTER
#include <Python.h>
#include "JupyterConnection.h"
#include "NestedIPyKernel.h"
#include "PythonManager.h"
#include "QtResImporter.h"
#include <QVariant>
#include <QDebug>
Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance)
JupyterConnection *JupyterConnection::getInstance()
{
return uniqueInstance;
}
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
{
connect(Python(), &PythonManager::willShutDown, this, &JupyterConnection::stop);
}
JupyterConnection::~JupyterConnection()
{
}
void JupyterConnection::start()
{
if (notebookInstanceExists) {
return;
}
notebookInstanceExists = startJupyterNotebook();
emit urlReceived(getUrl());
}
void JupyterConnection::stop()
{
if (cutterNotebookAppInstance) {
Python()->restoreThread();
auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop");
PyObject_CallObject(stopFunc, nullptr);
Py_DECREF(cutterNotebookAppInstance);
Python()->saveThread();
}
}
QString JupyterConnection::getUrl()
{
if (!notebookInstanceExists) {
return nullptr;
}
QString url = getJupyterUrl();
return url;
}
long JupyterConnection::startNestedIPyKernel(const QStringList &argv)
{
NestedIPyKernel *kernel = NestedIPyKernel::start(argv);
if (!kernel) {
qWarning() << "Could not start nested IPyKernel.";
return 0;
}
long id = nextKernelId++;
kernels.insert(id, kernel);
return id;
}
NestedIPyKernel *JupyterConnection::getNestedIPyKernel(long id)
{
auto it = kernels.find(id);
if (it == kernels.end()) {
return nullptr;
}
return *it;
}
QVariant JupyterConnection::pollNestedIPyKernel(long id)
{
auto it = kernels.find(id);
if (it == kernels.end()) {
return QVariant(0);
}
NestedIPyKernel *kernel = *it;
QVariant v = kernel->poll();
if (!v.isNull()) {
// if poll of kernel returns anything but None, it has already quit and should be cleaned up
PyThreadState *subinterpreterState = kernel->getThreadState();
delete kernel;
kernels.erase(it);
PyThreadState *parentThreadState = PyThreadState_Swap(subinterpreterState);
Py_EndInterpreter(subinterpreterState);
PyThreadState_Swap(parentThreadState);
}
return v;
}
bool JupyterConnection::startJupyterNotebook()
{
PythonManager::ThreadHolder threadHolder;
if (!cutterJupyterModule) {
cutterJupyterModule = QtResImport("cutter_jupyter");
if (!cutterJupyterModule) {
qWarning() << "Failed to load cutter_jupyter module. Make sure jupyter is installed.";
return false;
}
}
PyObject *startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter");
if (!startFunc) {
qWarning() << "Couldn't get attribute start_jupyter.";
return false;
}
cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr);
return cutterNotebookAppInstance != nullptr;
}
QString JupyterConnection::getJupyterUrl()
{
Python()->restoreThread();
auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token");
auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken);
auto urlWithTokenString = QString::fromUtf8(PyBytes_AsString(asciiBytes));
Py_DECREF(asciiBytes);
Py_DECREF(urlWithToken);
Python()->saveThread();
return urlWithTokenString;
}
#endif

View File

@ -1,54 +0,0 @@
#ifndef JUPYTERCONNECTION_H
#define JUPYTERCONNECTION_H
#ifdef CUTTER_ENABLE_JUPYTER
#include <QProcess>
#include <QMap>
class NestedIPyKernel;
typedef struct _object PyObject;
class JupyterConnection : public QObject
{
Q_OBJECT
public:
static JupyterConnection *getInstance();
JupyterConnection(QObject *parent = nullptr);
~JupyterConnection();
void start();
QString getUrl();
long startNestedIPyKernel(const QStringList &argv);
NestedIPyKernel *getNestedIPyKernel(long id);
QVariant pollNestedIPyKernel(long id);
public slots:
void stop();
signals:
void urlReceived(const QString &url);
void creationFailed();
private:
QMap<long, NestedIPyKernel *> kernels;
long nextKernelId = 1;
bool notebookInstanceExists = false;
PyObject *cutterJupyterModule = nullptr;
PyObject *cutterNotebookAppInstance = nullptr;
bool startJupyterNotebook();
QString getJupyterUrl();
};
#define Jupyter() (JupyterConnection::getInstance())
#endif // CUTTER_ENABLE_JUPYTER
#endif // JUPYTERCONNECTION_H

View File

@ -1,87 +0,0 @@
#ifdef CUTTER_ENABLE_JUPYTER
#include <Python.h>
#include <csignal>
#include "core/Cutter.h"
#include "NestedIPyKernel.h"
#include "QtResImporter.h"
NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv)
{
PyThreadState *parentThreadState = PyThreadState_Get();
PyThreadState *threadState = Py_NewInterpreter();
if (!threadState) {
qWarning() << "Could not create subinterpreter.";
return nullptr;
}
RegQtResImporter();
auto cutterIPykernelModule = QtResImport("cutter_ipykernel");
if (!cutterIPykernelModule) {
qWarning() << "Could not import cutter_ipykernel.";
return nullptr;
}
auto kernel = new NestedIPyKernel(cutterIPykernelModule, argv);
PyThreadState_Swap(parentThreadState);
return kernel;
}
NestedIPyKernel::NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv)
{
threadState = PyThreadState_Get();
auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel");
PyObject *argvListObject = PyList_New(argv.size());
for (int i = 0; i < argv.size(); i++) {
QString s = argv[i];
PyList_SetItem(argvListObject, i, PyUnicode_DecodeUTF8(s.toUtf8().constData(), s.length(),
nullptr));
}
kernel = PyObject_CallFunction(launchFunc, "O", argvListObject);
}
NestedIPyKernel::~NestedIPyKernel()
{
auto parentThreadState = PyThreadState_Swap(threadState);
auto ret = PyObject_CallMethod(kernel, "cleanup", nullptr);
if (!ret) {
PyErr_Print();
}
PyThreadState_Swap(parentThreadState);
}
void NestedIPyKernel::sendSignal(long signum)
{
auto parentThreadState = PyThreadState_Swap(threadState);
auto ret = PyObject_CallMethod(kernel, "send_signal", "l", signum);
if (!ret) {
PyErr_Print();
}
PyThreadState_Swap(parentThreadState);
}
QVariant NestedIPyKernel::poll()
{
QVariant ret;
auto parentThreadState = PyThreadState_Swap(threadState);
PyObject *pyRet = PyObject_CallMethod(kernel, "poll", nullptr);
if (pyRet) {
if (PyLong_Check(pyRet)) {
ret = (qlonglong)PyLong_AsLong(pyRet);
}
} else {
PyErr_Print();
}
PyThreadState_Swap(parentThreadState);
return ret;
}
#endif

View File

@ -1,38 +0,0 @@
#ifndef NESTEDIPYKERNEL_H
#define NESTEDIPYKERNEL_H
#ifdef CUTTER_ENABLE_JUPYTER
#include <QStringList>
struct _object;
typedef _object PyObject;
struct _ts;
typedef _ts PyThreadState;
class NestedIPyKernel
{
public:
static NestedIPyKernel *start(const QStringList &argv);
~NestedIPyKernel();
void sendSignal(long signum);
QVariant poll();
PyThreadState *getThreadState()
{
return threadState;
}
private:
NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv);
PyThreadState *threadState;
PyObject *kernel;
};
#endif
#endif //NESTEDIPYKERNEL_H

View File

@ -84,118 +84,4 @@ PyObject *PyInit_api()
return PyModule_Create(&CutterModule);
}
// -----------------------------
#ifdef CUTTER_ENABLE_JUPYTER
#include "JupyterConnection.h"
#include "NestedIPyKernel.h"
PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject *kw)
{
Q_UNUSED(self);
Q_UNUSED(kw);
QStringList argv;
PyObject *argvListObject;
if (!PyArg_ParseTuple(args, "O", &argvListObject)
|| !PyList_Check(argvListObject)) {
const char *msg = "Invalid args passed to api_internal_launch_ipykernel().";
qWarning() << msg;
PyErr_SetString(PyExc_RuntimeError, msg);
return nullptr;
}
for (int i = 0; i < PyList_Size(argvListObject); i++) {
PyObject *o = PyList_GetItem(argvListObject, i);
QString s = QString::fromUtf8(PyUnicode_AsUTF8(o));
argv.append(s);
}
long id = Jupyter()->startNestedIPyKernel(argv);
return PyLong_FromLong(id);
}
PyObject *api_internal_kernel_interface_send_signal(PyObject *, PyObject *args)
{
long id;
long signum;
if (!PyArg_ParseTuple(args, "ll", &id, &signum)) {
const char *msg = "Invalid args passed to api_internal_kernel_interface_send_signal().";
qWarning() << msg;
PyErr_SetString(PyExc_RuntimeError, msg);
return nullptr;
}
NestedIPyKernel *kernel = Jupyter()->getNestedIPyKernel(id);
if (kernel) {
kernel->sendSignal(signum);
}
Py_RETURN_NONE;
}
PyObject *api_internal_kernel_interface_poll(PyObject *, PyObject *args)
{
long id;
if (!PyArg_ParseTuple(args, "l", &id)) {
const char *msg = "Invalid args passed to api_internal_kernel_interface_poll().";
qWarning() << msg;
PyErr_SetString(PyExc_RuntimeError, msg);
return nullptr;
}
QVariant v = Jupyter()->pollNestedIPyKernel(id);
bool ok;
auto ret = static_cast<long>(v.toLongLong(&ok));
if (ok) {
return PyLong_FromLong(ret);
} else {
Py_RETURN_NONE;
}
}
PyObject *api_internal_thread_set_async_exc(PyObject *, PyObject *args)
{
long id;
PyObject *exc;
if (!PyArg_ParseTuple(args, "lO", &id, &exc)) {
const char *msg = "Invalid args passed to api_internal_thread_set_async_exc().";
qWarning() << msg;
PyErr_SetString(PyExc_RuntimeError, msg);
return nullptr;
}
int ret = PyThreadState_SetAsyncExc(id, exc);
return PyLong_FromLong(ret);
}
PyMethodDef CutterInternalMethods[] = {
{
"launch_ipykernel", reinterpret_cast<PyCFunction>((void *)api_internal_launch_ipykernel), METH_VARARGS | METH_KEYWORDS,
"Launch an IPython Kernel in a subinterpreter"
},
{"kernel_interface_send_signal", (PyCFunction)api_internal_kernel_interface_send_signal, METH_VARARGS, ""},
{"kernel_interface_poll", (PyCFunction)api_internal_kernel_interface_poll, METH_VARARGS, ""},
{"thread_set_async_exc", (PyCFunction)api_internal_thread_set_async_exc, METH_VARARGS, ""},
{NULL, NULL, 0, NULL}
};
PyModuleDef CutterInternalModule = {
PyModuleDef_HEAD_INIT, "cutter_internal", NULL, -1, CutterInternalMethods,
NULL, NULL, NULL, NULL
};
PyObject *PyInit_api_internal()
{
return PyModule_Create(&CutterInternalModule);
}
#endif // CUTTER_ENABLE_JUPYTER
#endif // CUTTER_ENABLE_PYTHON

View File

@ -74,9 +74,6 @@ void PythonManager::initialize()
initPythonHome();
PyImport_AppendInittab("_cutter", &PyInit_api);
#ifdef CUTTER_ENABLE_JUPYTER
PyImport_AppendInittab("cutter_internal", &PyInit_api_internal);
#endif
PyImport_AppendInittab("_qtres", &PyInit_qtres);
#ifdef CUTTER_ENABLE_PYTHON_BINDINGS
PyImport_AppendInittab("CutterBindings", &PyInit_CutterBindings);

View File

@ -52,7 +52,6 @@
#include "widgets/ClassesWidget.h"
#include "widgets/ResourcesWidget.h"
#include "widgets/VTablesWidget.h"
#include "widgets/JupyterWidget.h"
#include "widgets/HeadersWidget.h"
#include "widgets/ZignaturesWidget.h"
#include "widgets/DebugActions.h"
@ -268,12 +267,6 @@ void MainWindow::initDocks()
memoryMapDock = new MemoryMapWidget(this, ui->actionMemoryMap);
breakpointDock = new BreakpointWidget(this, ui->actionBreakpoint);
registerRefsDock = new RegisterRefsWidget(this, ui->actionRegisterRefs);
#ifdef CUTTER_ENABLE_JUPYTER
jupyterDock = new JupyterWidget(this, ui->actionJupyter);
#else
ui->actionJupyter->setEnabled(false);
ui->actionJupyter->setVisible(false);
#endif
dashboardDock = new Dashboard(this, ui->actionDashboard);
sdbDock = new SdbWidget(this, ui->actionSDBBrowser);
classesDock = new ClassesWidget(this, ui->actionClasses);
@ -790,9 +783,6 @@ void MainWindow::restoreDocks()
tabifyDockWidget(dashboardDock, memoryMapDock);
tabifyDockWidget(dashboardDock, breakpointDock);
tabifyDockWidget(dashboardDock, registerRefsDock);
#ifdef CUTTER_ENABLE_JUPYTER
tabifyDockWidget(dashboardDock, jupyterDock);
#endif
updateDockActionsChecked();
}
@ -827,9 +817,6 @@ void MainWindow::showZenDocks()
hexdumpDock,
searchDock,
importsDock,
#ifdef CUTTER_ENABLE_JUPYTER
jupyterDock
#endif
};
for (auto w : dockWidgets) {
if (zenDocks.contains(w)) {

View File

@ -43,9 +43,6 @@ class TypesWidget;
class HeadersWidget;
class ZignaturesWidget;
class SearchWidget;
#ifdef CUTTER_ENABLE_JUPYTER
class JupyterWidget;
#endif
class QDockWidget;
class DisassemblyWidget;
class GraphWidget;
@ -251,9 +248,6 @@ private:
NewFileDialog *newFileDialog = nullptr;
QDockWidget *breakpointDock = nullptr;
QDockWidget *registerRefsDock = nullptr;
#ifdef CUTTER_ENABLE_JUPYTER
JupyterWidget *jupyterDock = nullptr;
#endif
void initToolBar();
void initDocks();

View File

@ -213,7 +213,6 @@ QToolTip {
<addaction name="separator"/>
<addaction name="actionComments"/>
<addaction name="actionConsole"/>
<addaction name="actionJupyter"/>
<addaction name="separator"/>
<addaction name="menuAddExtraWidget"/>
<addaction name="menuPlugins"/>
@ -1107,14 +1106,6 @@ QToolTip {
<string>Show/Hide Zignatures panel</string>
</property>
</action>
<action name="actionJupyter">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Jupyter</string>
</property>
</action>
<action name="actionExport_as_code">
<property name="text">
<string>Export as code</string>

View File

@ -30,15 +30,15 @@ AboutDialog::AboutDialog(QWidget *parent) :
+ tr("Version") + " " CUTTER_VERSION_FULL "<br/>"
+ tr("Using r2-") + R2_GITTAP
+ "<p><b>" + tr("Optional Features:") + "</b><br/>"
+ QString("Jupyter: %1<br/>").arg(
#ifdef CUTTER_ENABLE_JUPYTER
+ QString("Python: %1<br/>").arg(
#ifdef CUTTER_ENABLE_PYTHON
"ON"
#else
"OFF"
#endif
)
+ QString("QtWebEngine: %2</p>").arg(
#ifdef CUTTER_ENABLE_QTWEBENGINE
+ QString("Python Bindings: %2</p>").arg(
#ifdef CUTTER_ENABLE_PYTHON_BINDINGS
"ON"
#else
"OFF"

View File

@ -13,16 +13,6 @@ if get_option('enable_python')
message('Python Bindings are enabled')
feature_define_args += ['-DCUTTER_ENABLE_PYTHON_BINDINGS']
endif
if get_option('enable_jupyter')
message('Jupyter support enabled')
feature_define_args += ['-DCUTTER_ENABLE_JUPYTER']
if get_option('enable_webengine')
message('QtWebEngine support enabled')
feature_define_args += ['-DCUTTER_ENABLE_QTWEBENGINE']
qt_modules += 'WebEngineWidgets'
endif
endif
endif
add_project_arguments(feature_define_args, language: 'cpp')

View File

@ -1,4 +1,2 @@
option('enable_python', type: 'boolean', value: true)
option('enable_python_bindings', type: 'boolean', value: true)
option('enable_jupyter', type: 'boolean', value: false)
option('enable_webengine', type: 'boolean', value: false)

View File

@ -1,84 +0,0 @@
import logging
import threading
import signal
import cutter_internal
import zmq
from ipykernel.kernelapp import IPKernelApp
from ipykernel.ipkernel import IPythonKernel
class IPyKernelInterfaceKernel:
def __init__(self, thread, app):
self._thread = thread
self._app = app
def send_signal(self, signum):
if not self._thread.is_alive():
return
if signum == signal.SIGKILL or signum == signal.SIGTERM:
self._app.io_loop.stop()
elif signum == signal.SIGINT and self._app.kernel.interruptable:
self._app.log.debug("Sending KeyboardInterrupt to ioloop thread.")
cutter_internal.thread_set_async_exc(self._thread.ident, KeyboardInterrupt())
def poll(self):
if self._thread.is_alive():
return None
else:
return 0
def cleanup(self):
self._app.heartbeat.context.destroy()
self._thread.join()
self._app.heartbeat.join()
self._app.iopub_thread.stop()
try:
self._app.kernel.shell.history_manager.save_thread.stop()
except AttributeError:
pass
zmq.Context.instance().destroy()
# successful if only the main thread remains
return len(threading.enumerate()) == 1
class CutterIPythonKernel(IPythonKernel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.interruptable = False
def pre_handler_hook(self):
self.interruptable = True
def post_handler_hook(self):
self.interruptable = False
class CutterIPKernelApp(IPKernelApp):
def init_signal(self):
# This would call signal.signal(signal.SIGINT, signal.SIG_IGN)
# Not needed in supinterpreter.
pass
def log_connection_info(self):
# Just skip this. It would only pollute Cutter's output.
pass
def launch_ipykernel(argv):
app = CutterIPKernelApp.instance()
def run_kernel():
import asyncio
asyncio.set_event_loop(asyncio.new_event_loop())
app.kernel_class = CutterIPythonKernel
# app.log_level = logging.DEBUG
app.initialize(argv[3:])
app.start()
thread = threading.Thread(target=run_kernel)
thread.start()
return IPyKernelInterfaceKernel(thread, app)

View File

@ -1,140 +0,0 @@
import asyncio
import queue
import sys
from jupyter_client.ioloop import IOLoopKernelManager
from notebook.notebookapp import *
import cutter_internal
class IPyKernelInterfaceJupyter:
def __init__(self, id):
self._id = id
def send_signal(self, signum):
cutter_internal.kernel_interface_send_signal(self._id, signum)
def kill(self):
self.send_signal(signal.SIGKILL)
def terminate(self):
self.send_signal(signal.SIGTERM)
def poll(self):
return cutter_internal.kernel_interface_poll(self._id)
def wait(self, timeout=None):
if timeout is not None:
start_time = time.process_time()
else:
start_time = None
while timeout is None or time.process_time() - start_time < timeout:
if self.poll() is not None:
return
time.sleep(0.1)
class CutterInternalIPyKernelManager(IOLoopKernelManager):
def start_kernel(self, **kw):
self.write_connection_file()
self._launch_args = kw.copy()
extra_arguments = kw.pop('extra_arguments', [])
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
env = kw.pop('env', os.environ).copy()
# Don't allow PYTHONEXECUTABLE to be passed to kernel process.
# If set, it can bork all the things.
env.pop('PYTHONEXECUTABLE', None)
if not self.kernel_cmd:
# If kernel_cmd has been set manually, don't refer to a kernel spec
# Environment variables from kernel spec are added to os.environ
env.update(self.kernel_spec.env or {})
# launch the kernel subprocess
id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw)
self.kernel = IPyKernelInterfaceJupyter(id)
# self._launch_kernel(kernel_cmd, env=env, **kw)
self.start_restarter()
self._connect_control_socket()
def signal_kernel(self, signum):
self.kernel.send_signal(signum)
def kernel_manager_factory(kernel_name, **kwargs):
if kernel_name in {"python", "python2", "python3"}:
return CutterInternalIPyKernelManager(kernel_name=kernel_name, **kwargs)
else:
return IOLoopKernelManager(kernel_name=kernel_name, **kwargs)
class CutterNotebookApp(NotebookApp):
def __init__(self, **kwargs):
self.thread = None
self.io_loop = None
super().__init__(**kwargs)
def start(self):
""" see NotebookApp.start() """
self.kernel_manager.kernel_manager_factory = kernel_manager_factory
super(NotebookApp, self).start()
self.write_server_info_file()
self.io_loop = ioloop.IOLoop.current()
if sys.platform.startswith('win'):
# add no-op to wake every 5s
# to handle signals that may be ignored by the inner loop
pc = ioloop.PeriodicCallback(lambda: None, 5000)
pc.start()
try:
self.io_loop.start()
except KeyboardInterrupt:
self.log.info(_("Interrupted..."))
finally:
self.remove_server_info_file()
self.cleanup_kernels()
def stop(self):
super().stop()
if self.thread is not None:
self.thread.join()
def init_signal(self):
# This would call signal.signal(signal.SIGINT, signal.SIG_IGN)
# Not needed in supinterpreter.
pass
@property
def url_with_token(self):
return url_concat(self.connection_url, {'token': self.token})
def start_jupyter():
q = queue.Queue()
def start_jupyter_async():
# workaround for misbehavior under certain circumstances
# with argumentparser and jupyter accessing out of bounds
# program name via argv[0]
if not sys.argv:
sys.argv.append("Cutter")
asyncio.set_event_loop(asyncio.new_event_loop())
app = CutterNotebookApp()
# app.log_level = logging.DEBUG
app.thread = threading.current_thread()
app.initialize()
q.put(app)
app.start()
threading.Thread(target=start_jupyter_async).start()
return q.get()
if __name__ == "__main__":
app = start_jupyter()
print("Started " + app.url_with_token)

View File

@ -77,8 +77,6 @@
<file>img/cutter_plain.svg</file>
<file>img/cutter_white_plain.svg</file>
<file>img/cutter.svg</file>
<file>python/cutter_jupyter.py</file>
<file>python/cutter_ipykernel.py</file>
<file>img/icons/copy.svg</file>
<file>img/icons/delete.svg</file>
<file>img/icons/previous.svg</file>

View File

@ -1,179 +0,0 @@
#ifdef CUTTER_ENABLE_JUPYTER
#include "common/JupyterConnection.h"
#include "JupyterWidget.h"
#include "ui_JupyterWidget.h"
#include <QTabWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#ifdef CUTTER_ENABLE_QTWEBENGINE
#include <QWebEngineSettings>
#endif
JupyterWidget::JupyterWidget(MainWindow *main, QAction *action) :
CutterDockWidget(main, action),
ui(new Ui::JupyterWidget)
{
ui->setupUi(this);
ui->tabWidget->setTabsClosable(true);
ui->tabWidget->setMovable(true);
QWidget *cornerWidget = new QWidget(ui->tabWidget);
QHBoxLayout *cornerWidgetLayout = new QHBoxLayout(cornerWidget);
cornerWidget->setLayout(cornerWidgetLayout);
cornerWidgetLayout->setContentsMargins(4, 4, 4, 4);
homeButton = new QPushButton(cornerWidget);
homeButton->setStyleSheet("QPushButton { padding: 2px; background-color: palette(light); border-radius: 4px; }"
"QPushButton:pressed { background-color: palette(dark); }");
homeButton->setIcon(QIcon(":/img/icons/home.svg"));
homeButton->setEnabled(false);
cornerWidgetLayout->addWidget(homeButton);
ui->tabWidget->setCornerWidget(cornerWidget);
connect(homeButton, &QAbstractButton::clicked, this, &JupyterWidget::openHomeTab);
connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, &JupyterWidget::tabCloseRequested);
connect(Jupyter(), &JupyterConnection::urlReceived, this, &JupyterWidget::urlReceived);
connect(Jupyter(), &JupyterConnection::creationFailed, this, &JupyterWidget::creationFailed);
Jupyter()->start();
}
JupyterWidget::~JupyterWidget()
{
}
#ifdef CUTTER_ENABLE_QTWEBENGINE
JupyterWebView *JupyterWidget::createNewTab()
{
auto webView = new JupyterWebView(this);
int index = ui->tabWidget->addTab(webView, "Tab");
webView->setTabWidget(ui->tabWidget);
ui->tabWidget->setCurrentIndex(index);
return webView;
}
#endif
void JupyterWidget::urlReceived(const QString &url)
{
#ifdef CUTTER_ENABLE_QTWEBENGINE
Q_UNUSED(url);
openHomeTab();
homeButton->setEnabled(true);
#else
clearTabs();
QWidget *failPage = new QWidget(this);
QLabel *label = new QLabel(failPage);
label->setText(
tr("Cutter has been built without QtWebEngine.<br />Open the following URL in your Browser to use Jupyter:<br /><a href=\"%1\">%1</a>").arg(
url));
label->setTextFormat(Qt::RichText);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setOpenExternalLinks(true);
QHBoxLayout *layout = new QHBoxLayout();
layout->addWidget(label);
layout->setAlignment(label, Qt::AlignCenter);
failPage->setLayout(layout);
ui->tabWidget->addTab(failPage, tr("Jupyter"));
homeButton->setEnabled(false);
ui->tabWidget->setTabsClosable(false);
#endif
}
void JupyterWidget::creationFailed()
{
clearTabs();
QWidget *failPage = new QWidget(this);
QLabel *label = new QLabel(failPage);
label->setText(
tr("An error occurred while opening jupyter. Make sure Jupyter is installed system-wide."));
QHBoxLayout *layout = new QHBoxLayout();
layout->addWidget(label);
layout->setAlignment(label, Qt::AlignCenter);
failPage->setLayout(layout);
ui->tabWidget->addTab(failPage, tr("Error"));
homeButton->setEnabled(false);
ui->tabWidget->setTabsClosable(false);
}
void JupyterWidget::openHomeTab()
{
#ifdef CUTTER_ENABLE_QTWEBENGINE
QString url = Jupyter()->getUrl();
if (!url.isNull()) {
createNewTab()->load(QUrl(url));
}
#endif
}
void JupyterWidget::tabCloseRequested(int index)
{
removeTab(index);
if (ui->tabWidget->count() == 0) {
openHomeTab();
}
}
void JupyterWidget::removeTab(int index)
{
QWidget *widget = ui->tabWidget->widget(index);
ui->tabWidget->removeTab(index);
delete widget;
}
void JupyterWidget::clearTabs()
{
while (ui->tabWidget->count() > 0) {
removeTab(0);
}
}
#ifdef CUTTER_ENABLE_QTWEBENGINE
JupyterWebView::JupyterWebView(JupyterWidget *mainWidget, QWidget *parent) : QWebEngineView(parent)
{
this->mainWidget = mainWidget;
this->tabWidget = nullptr;
connect(this, &QWebEngineView::titleChanged, this, &JupyterWebView::onTitleChanged);
}
void JupyterWebView::setTabWidget(QTabWidget *tabWidget)
{
this->tabWidget = tabWidget;
updateTitle();
}
QWebEngineView *JupyterWebView::createWindow(QWebEnginePage::WebWindowType type)
{
switch (type) {
case QWebEnginePage::WebBrowserTab:
return mainWidget->createNewTab();
default:
return nullptr;
}
}
void JupyterWebView::onTitleChanged(const QString &)
{
updateTitle();
}
void JupyterWebView::updateTitle()
{
if (!tabWidget) {
return;
}
QString title = this->title();
if (title.isEmpty()) {
title = tr("Jupyter");
}
tabWidget->setTabText(tabWidget->indexOf(this), title);
}
#endif
#endif

View File

@ -1,78 +0,0 @@
#ifndef JUPYTERWIDGET_H
#define JUPYTERWIDGET_H
#ifdef CUTTER_ENABLE_JUPYTER
#include "CutterDockWidget.h"
#include <memory>
#include <QAbstractButton>
namespace Ui {
class JupyterWidget;
}
class JupyterWebView;
class QTabWidget;
class MainWindow;
class JupyterWidget : public CutterDockWidget
{
Q_OBJECT
public:
JupyterWidget(MainWindow *main, QAction *action = nullptr);
~JupyterWidget();
#ifdef CUTTER_ENABLE_QTWEBENGINE
JupyterWebView *createNewTab();
#endif
private slots:
void urlReceived(const QString &url);
void creationFailed();
void openHomeTab();
void tabCloseRequested(int index);
private:
std::unique_ptr<Ui::JupyterWidget> ui;
QAbstractButton *homeButton;
void removeTab(int index);
void clearTabs();
};
#ifdef CUTTER_ENABLE_QTWEBENGINE
#include <QWebEngineView>
class JupyterWebView : public QWebEngineView
{
Q_OBJECT
public:
JupyterWebView(JupyterWidget *mainWidget, QWidget *parent = nullptr);
void setTabWidget(QTabWidget *tabWidget);
protected:
QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;
private slots:
void onTitleChanged(const QString &title);
private:
JupyterWidget *mainWidget;
QTabWidget *tabWidget;
void updateTitle();
};
#endif
#endif
#endif //JUPYTERWIDGET_H

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>JupyterWidget</class>
<widget class="QDockWidget" name="JupyterWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>644</width>
<height>484</height>
</rect>
</property>
<property name="windowTitle">
<string>Jupyter</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget"/>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>