diff --git a/debugger/debugjob.cpp b/debugger/debugjob.cpp index 19d8692d..b1215d9b 100644 --- a/debugger/debugjob.cpp +++ b/debugger/debugjob.cpp @@ -1,106 +1,107 @@ /* This file is part of kdev-python, the python language plugin for KDevelop Copyright (C) 2012 Sven Brauch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "debugjob.h" #include #include #include #include #include #include #include #include "debuggerdebug.h" namespace Python { void DebugJob::start() { QStringList program; QString debuggerUrl = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/debugger", QStandardPaths::LocateDirectory) + "/kdevpdb.py"; program << m_interpreter << "-u" << debuggerUrl << m_scriptUrl.toLocalFile() << m_args; - m_session = new DebugSession(program, m_workingDirectory); + // Inject environment + m_session = new DebugSession(program, m_workingDirectory, m_envProfileName); setStandardToolView(KDevelop::IOutputView::DebugView); setBehaviours(KDevelop::IOutputView::Behaviours(KDevelop::IOutputView::AllowUserClose) | KDevelop::IOutputView::AutoScroll); OutputModel* pyOutputModel = new KDevelop::OutputModel(); pyOutputModel->setFilteringStrategy(OutputModel::ScriptErrorFilter); setModel(pyOutputModel); setTitle(m_interpreter + m_scriptUrl.toLocalFile()); setModel(new KDevelop::OutputModel(nullptr)); startOutput(); qCDebug(KDEV_PYTHON_DEBUGGER) << "connecting standardOutputReceived"; connect(m_session, &DebugSession::realDataReceived, this, &DebugJob::standardOutputReceived); connect(m_session, &DebugSession::stderrReceived, this, &DebugJob::standardErrorReceived); connect(m_session, &KDevelop::IDebugSession::finished, this, &DebugJob::sessionFinished); KDevelop::ICore::self()->debugController()->addSession(m_session); m_session->start(); qCDebug(KDEV_PYTHON_DEBUGGER) << "starting program:" << program; } void DebugJob::sessionFinished() { emitResult(); } void DebugJob::standardErrorReceived(QStringList lines) { if ( OutputModel* m = outputModel() ) { m->appendLines(lines); } } void DebugJob::standardOutputReceived(QStringList lines) { qCDebug(KDEV_PYTHON_DEBUGGER) << "standard output received:" << lines << outputModel(); if ( OutputModel* m = outputModel() ) { m->appendLines(lines); } } OutputModel* DebugJob::outputModel() { return dynamic_cast(model()); } bool DebugJob::doKill() { qCDebug(KDEV_PYTHON_DEBUGGER) << "kill signal received"; m_session->stopDebugger(); return true; } DebugJob::DebugJob() { } DebugJob::~DebugJob() { } } diff --git a/debugger/debugjob.h b/debugger/debugjob.h index 828b4a51..1c779c07 100644 --- a/debugger/debugjob.h +++ b/debugger/debugjob.h @@ -1,60 +1,61 @@ /* This file is part of kdev-python, the python language plugin for KDevelop Copyright (C) 2012 Sven Brauch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PDBDEBUGJOB_H #define PDBDEBUGJOB_H #include #include #include "debugsession.h" namespace Python { class DebugJob : public KDevelop::OutputJob { Q_OBJECT public: DebugJob(); ~DebugJob() override; /** * @brief Create a debug session and start it. **/ void start() override; bool doKill() override; QUrl m_scriptUrl; QString m_interpreter; QStringList m_args; QUrl m_workingDirectory; + QString m_envProfileName; private slots: void standardOutputReceived(QStringList lines); void standardErrorReceived(QStringList lines); private: OutputModel* outputModel(); DebugSession* m_session; public slots: void sessionFinished(); }; } #endif // DEBUGJOB_H diff --git a/debugger/debugsession.cpp b/debugger/debugsession.cpp index 5a97a428..c736c226 100644 --- a/debugger/debugsession.cpp +++ b/debugger/debugsession.cpp @@ -1,512 +1,526 @@ /* This file is part of kdev-python, the python language plugin for KDevelop Copyright (C) 2012 Sven Brauch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include +#include #include "debugsession.h" #include "pdbframestackmodel.h" #include "variablecontroller.h" #include "variable.h" #include "breakpointcontroller.h" #include #include #include "debuggerdebug.h" #ifdef Q_OS_WIN #include #define INTERRUPT_DEBUGGER GenerateConsoleCtrlEvent(CTRL_C_EVENT, m_debuggerProcess->processId()) #else #define INTERRUPT_DEBUGGER kill(m_debuggerProcess->pid(), SIGINT) #endif using namespace KDevelop; static QByteArray debuggerPrompt = "__KDEVPYTHON_DEBUGGER_PROMPT"; static QByteArray debuggerOutputBegin = "__KDEVPYTHON_BEGIN_DEBUGGER_OUTPUT>>>"; static QByteArray debuggerOutputEnd = "<<<__KDEVPYTHON_END___DEBUGGER_OUTPUT"; namespace Python { -DebugSession::DebugSession(QStringList program, const QUrl &workingDirectory) : +DebugSession::DebugSession(QStringList program, const QUrl &workingDirectory, + const QString& envProfileName) : IDebugSession() , m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) , m_workingDirectory(workingDirectory) + , m_envProfileName(envProfileName) , m_nextNotifyMethod(nullptr) , m_inDebuggerData(0) { qCDebug(KDEV_PYTHON_DEBUGGER) << "creating debug session"; m_program = program; m_breakpointController = new Python::BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new PdbFrameStackModel(this); } IBreakpointController* DebugSession::breakpointController() const { return m_breakpointController; } IVariableController* DebugSession::variableController() const { return m_variableController; } IFrameStackModel* DebugSession::frameStackModel() const { return m_frameStackModel; } void DebugSession::start() { setState(StartingState); m_debuggerProcess = new KProcess(this); m_debuggerProcess->setProgram(m_program); m_debuggerProcess->setOutputChannelMode(KProcess::SeparateChannels); m_debuggerProcess->blockSignals(true); m_debuggerProcess->setWorkingDirectory(m_workingDirectory.path()); + + const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); + const auto environment = environmentProfiles.variables(m_envProfileName); + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + for(auto i = environment.cbegin(); i != environment.cend(); i++ ) + { + env.insert(i.key(), i.value()); + } + m_debuggerProcess->setProcessEnvironment(env); + connect(m_debuggerProcess, &QProcess::readyReadStandardOutput, this, &DebugSession::dataAvailable); connect(m_debuggerProcess, SIGNAL(finished(int)), this, SLOT(debuggerQuit(int))); connect(this, &DebugSession::debuggerReady, this, &DebugSession::checkCommandQueue); connect(this, &DebugSession::commandAdded, this, &DebugSession::checkCommandQueue); m_debuggerProcess->start(); m_debuggerProcess->waitForStarted(); auto dir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/debugger/", QStandardPaths::LocateDirectory); InternalPdbCommand* path = new InternalPdbCommand(nullptr, nullptr, "import sys; sys.path.append('"+dir+"')\n"); InternalPdbCommand* cmd = new InternalPdbCommand(nullptr, nullptr, "import __kdevpython_debugger_utils\n"); addCommand(path); addCommand(cmd); updateLocation(); m_debuggerProcess->blockSignals(false); } void DebugSession::debuggerQuit(int ) { setState(EndedState); } QStringList byteArrayToStringList(const QByteArray& r) { QStringList items; foreach ( const QByteArray& item, r.split('\n') ) { items << item.data(); } if ( r.endsWith('\n') ) { items.pop_back(); } return items; } void DebugSession::dataAvailable() { QByteArray data = m_debuggerProcess->readAllStandardOutput(); qCDebug(KDEV_PYTHON_DEBUGGER) << data.length() << "bytes of data available"; // remove pointless state changes data.replace(debuggerOutputBegin+debuggerOutputEnd, ""); data.replace(debuggerOutputEnd+debuggerOutputBegin, ""); bool endsWithPrompt = false; if ( data.endsWith(debuggerPrompt) ) { endsWithPrompt = true; // remove the prompt data = data.mid(0, data.length() - debuggerPrompt.length()); } // scan the data, and separate program output from debugger output int len = data.length(); int delimiterSkip = debuggerOutputEnd.length(); int i = 0; QByteArray realData; while ( i < len ) { int nextChangeAt = data.indexOf(m_inDebuggerData ? debuggerOutputEnd : debuggerOutputBegin, i); bool atLastChange = nextChangeAt == -1; nextChangeAt = atLastChange ? len : qMin(nextChangeAt, len); qCDebug(KDEV_PYTHON_DEBUGGER) << data; Q_ASSERT(m_inDebuggerData == 0 || m_inDebuggerData == 1); if ( m_inDebuggerData == 1 ) { QString newDebuggerData = data.mid(i, nextChangeAt - i); m_buffer.append(newDebuggerData); if ( data.indexOf("Uncaught exception. Entering post mortem debugging") != -1 ) { emit realDataReceived(QStringList() << "*****" << " " + i18n("The program being debugged raised an uncaught exception.") << " " + i18n("You can now inspect the status of the program after it exited.") << " " + i18n("The debugger will silently stop when the next command is triggered.") << "*****"); InternalPdbCommand* cmd = new InternalPdbCommand(nullptr, nullptr, "import __kdevpython_debugger_utils\n"); addCommand(cmd); } } else if ( m_inDebuggerData == 0 ) { QByteArray d = data.mid(i, nextChangeAt - i); if ( d.length() > 0 ) { realData.append(d); } } i = nextChangeAt + delimiterSkip; if ( m_inDebuggerData != 1 ) m_inDebuggerData = 1; else m_inDebuggerData = 0; if ( atLastChange ) { break; } } while (int index = realData.indexOf(debuggerPrompt) != -1 ) { realData.remove(index-1, debuggerPrompt.length()); } if ( ! realData.isEmpty() ) { // FIXME this is not very elegant. QStringList items = byteArrayToStringList(realData); emit realDataReceived(items); } // Although unbuffered, it seems guaranteed that the debugger prompt is written at once. // I don't think a python statement like print "FooBar" will ever break the output into two parts. // TODO find explicit documentation for this somewhere. if ( endsWithPrompt ) { if ( state() == StartingState ) { setState(PausedState); raiseEvent(connected_to_program); } else { notifyNext(); if ( m_commandQueue.isEmpty() ) { qCDebug(KDEV_PYTHON_DEBUGGER) << "Changing state to PausedState"; setState(PausedState); } } m_processBusy = false; emit debuggerReady(); } data = m_debuggerProcess->readAllStandardError(); if ( ! data.isEmpty() ) { emit stderrReceived(byteArrayToStringList(data)); } } void DebugSession::setNotifyNext(QPointer object, const char* method) { qCDebug(KDEV_PYTHON_DEBUGGER) << "set notify next:" << object << method; m_nextNotifyObject = object; m_nextNotifyMethod = method; } void DebugSession::notifyNext() { qCDebug(KDEV_PYTHON_DEBUGGER) << "notify next:" << m_nextNotifyObject << this; if ( m_nextNotifyMethod && m_nextNotifyObject ) { QMetaObject::invokeMethod(m_nextNotifyObject.data(), m_nextNotifyMethod, Qt::DirectConnection, Q_ARG(QByteArray, m_buffer)); } else { qCDebug(KDEV_PYTHON_DEBUGGER) << "notify called, but nothing to notify!"; } m_buffer.clear(); m_nextNotifyMethod = nullptr; m_nextNotifyObject.clear(); } void DebugSession::processNextCommand() { qCDebug(KDEV_PYTHON_DEBUGGER) << "processing next debugger command in queue"; if ( m_processBusy || m_state == EndedState ) { qCDebug(KDEV_PYTHON_DEBUGGER) << "process is busy or ended, aborting"; return; } m_processBusy = true; PdbCommand* cmd = m_commandQueue.first(); Q_ASSERT(cmd->type() != PdbCommand::InvalidType); if ( cmd->type() == PdbCommand::UserType ) { setState(ActiveState); } m_commandQueue.removeFirst(); setNotifyNext(cmd->notifyObject(), cmd->notifyMethod()); cmd->run(this); qCDebug(KDEV_PYTHON_DEBUGGER) << "command executed, deleting it."; delete cmd; if ( ! m_commandQueue.isEmpty() ) { processNextCommand(); } } void DebugSession::setState(DebuggerState state) { qCDebug(KDEV_PYTHON_DEBUGGER) << "Setting state to" << state; if ( state == m_state ) { return; } m_state = state; if ( m_state == EndedState ) { raiseEvent(debugger_exited); emit finished(); } else if ( m_state == ActiveState || m_state == StartingState || m_state == StoppingState ) { raiseEvent(debugger_busy); } else if ( m_state == PausedState ) { raiseEvent(debugger_ready); if ( currentUrl().isValid() ) { emit showStepInSource(currentUrl(), currentLine(), currentAddr()); } } qCDebug(KDEV_PYTHON_DEBUGGER) << "debugger state changed to" << m_state; raiseEvent(program_state_changed); emit stateChanged(m_state); } void DebugSession::write(const QByteArray& cmd) { qCDebug(KDEV_PYTHON_DEBUGGER) << " >>> WRITE:" << cmd; m_debuggerProcess->write(cmd); } void DebugSession::stepOut() { // TODO this only steps out of functions; use temporary breakpoints for loops maybe? addSimpleUserCommand("return"); } void DebugSession::stepOverInstruction() { addSimpleUserCommand("next"); } void DebugSession::stepInto() { addSimpleUserCommand("step"); } void DebugSession::stepIntoInstruction() { addSimpleUserCommand("step"); } void DebugSession::stepOver() { addSimpleUserCommand("next"); } void DebugSession::jumpToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if ( cursor.isValid() ) { // TODO disable all other breakpoints addSimpleUserCommand(QString("jump " + QString::number(cursor.line() + 1)).toUtf8()); } } } void DebugSession::runToCursor() { if (KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if ( cursor.isValid() ) { // TODO disable all other breakpoints QString temporaryBreakpointLocation = doc->url().path() + ':' + QString::number(cursor.line() + 1); InternalPdbCommand* temporaryBreakpointCmd = new InternalPdbCommand(nullptr, nullptr, "tbreak " + temporaryBreakpointLocation + '\n'); addCommand(temporaryBreakpointCmd); addSimpleInternalCommand("continue"); updateLocation(); } } } void DebugSession::run() { addSimpleUserCommand("continue"); } void DebugSession::interruptDebugger() { INTERRUPT_DEBUGGER; updateLocation(); setState(PausedState); } void DebugSession::addCommand(PdbCommand* cmd) { if ( m_state == EndedState || m_state == StoppingState ) { return; } qCDebug(KDEV_PYTHON_DEBUGGER) << " +++ adding command to queue:" << cmd; m_commandQueue.append(cmd); if ( cmd->type() == PdbCommand::UserType ) { // this is queued and will run after the command is executed. updateLocation(); } emit commandAdded(); } void DebugSession::checkCommandQueue() { qCDebug(KDEV_PYTHON_DEBUGGER) << "items in queue:" << m_commandQueue.length(); if ( m_commandQueue.isEmpty() ) { return; } processNextCommand(); } void DebugSession::clearObjectTable() { addSimpleInternalCommand("__kdevpython_debugger_utils.cleanup()"); } void DebugSession::addSimpleUserCommand(const QString& cmd) { clearObjectTable(); UserPdbCommand* cmdObject = new UserPdbCommand(nullptr, nullptr, cmd + '\n'); Q_ASSERT(cmdObject->type() == PdbCommand::UserType); addCommand(cmdObject); } void DebugSession::addSimpleInternalCommand(const QString& cmd) { Q_ASSERT( ! cmd.endsWith('\n') ); InternalPdbCommand* cmdObject = new InternalPdbCommand(nullptr, nullptr, cmd + '\n'); addCommand(cmdObject); } void DebugSession::runImmediately(const QString& cmd) { Q_ASSERT(cmd.endsWith('\n')); if ( state() == ActiveState ) { m_nextNotifyMethod = nullptr; m_nextNotifyObject.clear(); // TODO is this correct? qCDebug(KDEV_PYTHON_DEBUGGER) << "interrupting debugger"; INTERRUPT_DEBUGGER; write(cmd.toUtf8()); write("continue\n"); updateLocation(); } else { addCommand(new InternalPdbCommand(nullptr, nullptr, cmd)); } } void DebugSession::addBreakpoint(Breakpoint* bp) { QString location = bp->url().path() + ":" + QString::number(bp->line() + 1); qCDebug(KDEV_PYTHON_DEBUGGER) << "adding breakpoint" << location; runImmediately("break " + location + '\n'); } void DebugSession::removeBreakpoint(Breakpoint* bp) { QString location = bp->url().path() + ":" + QString::number(bp->line() + 1); qCDebug(KDEV_PYTHON_DEBUGGER) << "deleting breakpoint" << location; runImmediately("clear " + location + '\n'); } void DebugSession::createVariable(Python::Variable* variable, QObject* callback, const char* callbackMethod) { qCDebug(KDEV_PYTHON_DEBUGGER) << "asked to create variable"; auto text = ("print(__kdevpython_debugger_utils.obj_to_string(" + variable->expression() + "))\n").toUtf8(); auto cmd = new InternalPdbCommand(variable, "dataFetched", text); variable->m_notifyCreated = callback; variable->m_notifyCreatedMethod = callbackMethod; addCommand(cmd); } void DebugSession::clearOutputBuffer() { m_buffer.clear(); } void DebugSession::updateLocation() { qCDebug(KDEV_PYTHON_DEBUGGER) << "updating location"; InternalPdbCommand* cmd = new InternalPdbCommand(this, "locationUpdateReady", "where\n"); addCommand(cmd); } void DebugSession::locationUpdateReady(QByteArray data) { qCDebug(KDEV_PYTHON_DEBUGGER) << "Got where information: " << data; QList lines = data.split('\n'); if ( lines.length() >= 3 ) { lines.removeLast(); // prompt lines.removeLast(); // source line QString where = lines.last(); // > /bar/baz/foo.py(123)() QRegExp m("^> (/.*\\.py)\\((\\d*)\\).*$"); m.setMinimal(true); m.exactMatch(where); setCurrentPosition(QUrl::fromLocalFile(m.capturedTexts().at(1)), m.capturedTexts().at(2).toInt() - 1 , ""); qCDebug(KDEV_PYTHON_DEBUGGER) << "New position: " << m.capturedTexts().at(1) << m.capturedTexts().at(2).toInt() - 1 << m.capturedTexts() << where; } } void DebugSession::stopDebugger() { m_commandQueue.clear(); InternalPdbCommand* cmd = new InternalPdbCommand(nullptr, nullptr, "quit\nquit\n"); addCommand(cmd); setState(StoppingState); if ( ! m_debuggerProcess->waitForFinished(200) ) { m_debuggerProcess->kill(); } m_commandQueue.clear(); m_nextNotifyMethod = nullptr; m_nextNotifyObject.clear(); qCDebug(KDEV_PYTHON_DEBUGGER) << "killed debugger"; setState(IDebugSession::EndedState); } DebugSession::~DebugSession() { m_debuggerProcess->kill(); } void DebugSession::restartDebugger() { addSimpleUserCommand("run"); } bool DebugSession::restartAvaliable() const { return false; } KDevelop::IDebugSession::DebuggerState DebugSession::state() const { return m_state; } } diff --git a/debugger/debugsession.h b/debugger/debugsession.h index c5840759..f9e3978b 100644 --- a/debugger/debugsession.h +++ b/debugger/debugsession.h @@ -1,348 +1,350 @@ /* This file is part of kdev-python, the python language plugin for KDevelop Copyright (C) 2012 Sven Brauch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PDBDEBUGSESSION_H #define PDBDEBUGSESSION_H #include #include #include #include #include "debuggerdebug.h" #include #include #include #include "variable.h" using namespace KDevelop; namespace Python { struct PdbCommand; class DebugSession : public KDevelop::IDebugSession { Q_OBJECT public: - DebugSession(QStringList program, const QUrl& workingDirectory); + DebugSession(QStringList program, const QUrl& workingDirectory, + const QString& envProfileName); ~DebugSession() override; IBreakpointController* breakpointController() const override; IFrameStackModel* frameStackModel() const override; /** * @brief Start the debugger. **/ void start(); /** * @brief Adds a command to the queue. * Commands are processed in the same order they're added. * If the type of the command added is UserType, automatic updates * (of local variables, location, ...) will be triggered. * * @param cmd The command to enqeue. **/ void addCommand(PdbCommand* cmd); /** * @brief Convenience function, constructs a new UserPdbCommand and enqueues it. * Use this to enqueue simple commands invoked by user clicks ("next" etc.) * * @param cmd What you would type at the debugger command line. **/ void addSimpleUserCommand(const QString& cmd); /** * @brief Convencience function, constructs a new InternalPdbCommand and enqueues it. * Use this to enqueue simple commands which are needed internally ("where", ...) * * @param cmd What you would type at the debugger command line. **/ void addSimpleInternalCommand(const QString& cmd); /** * @brief Interrupt the running program with SIGINT and immediately run the specified command. * This will also trigger a location update. Program execution will continue immediately after * the given command has been run! * * @param cmd What you would type at the debugger command line, terminated by \n. **/ void runImmediately(const QString& cmd); /** * @brief Constructs commands to add the given breakpoint to the debugger. * * @param bp The breakpoint to add **/ void addBreakpoint(Breakpoint* bp); /** * @brief Constructs commands to remove the given breakpoint from the debugger. * * @param bp The breakpoint to remove **/ void removeBreakpoint(Breakpoint* bp); /** * @brief Access this session's variable controller **/ IVariableController* variableController() const override; /// Those functions just execute the basic debugger commands. They're used when the user /// clicks the appropriate button. void stepOut() override; void stepOverInstruction() override; void stepInto() override; void stepIntoInstruction() override; void stepOver() override; void jumpToCursor() override; void runToCursor() override; void run() override; void restartDebugger() override; bool restartAvaliable() const override; /** * @brief Interrupt the running program with SIGINT and set the state to PausedState **/ void interruptDebugger() override; /** * @brief Kill the debugger and program being debugged. * This tries to send a "quit" command, and if the debugger doesn't react to that quickly, * it'll just kill it. **/ void stopDebugger() override; /** * @brief Gives the debugger state. * The two main states are "ActiveState" and "PausedState"; the former is given * if the *user program* is being run by the debugger. * * @return :IDebugSession::DebuggerState the current state the debugger is in **/ IDebugSession::DebuggerState state() const override; /** * @brief Change the debugger state, and trigger various events depending on the previous and new state. * WARNING: Do *not* switch to ActiveState for running internal commands: If * the location is being updated by "where", no state switching should occur. * Otherwise, various endless loops might occur because kdevplatform tries auto- * update various things (like the location, ...) * State changes should only occur when starting up, shutting down, or on explicit user interaction. * * @param state The state to change to. **/ void setState(IDebugSession::DebuggerState state); /** * @brief Enqueue a command which updates the location. * Run this whenever you enqueue a command which might change the location in the source code * (like "next" or similar). This is queued, so you can do addSimpleUserCommand("next"); updateLocation(); * without problems. **/ void updateLocation(); /** * @brief Clears the table of object IDs stored in the debugger script **/ void clearObjectTable(); /** * @brief Write raw data to the debugger process' stdin. * Remember that you have to terminate your input by "\n" for the debugger to process it. * * @param cmd data to write to stdin **/ void write(const QByteArray& cmd); public slots: /** * @brief Emitted when new data has been received from the debugger process (via stdout) **/ void dataAvailable(); /** * @brief Fetch the given variable's value and assign it, and when done call the given callback method. * * @param variable Variable object to fetch data for * @param callback object to call callbackMethod on * @param callbackMethod method to call when done **/ void createVariable(Python::Variable* variable, QObject* callback, const char* callbackMethod); /** * @brief Check the command queue, and run the next command if it's not empty. **/ void checkCommandQueue(); /** * @brief Performs a location update. * This is used by updateLocation(). **/ void locationUpdateReady(QByteArray data); void debuggerQuit(int); signals: /// Emitted when the debugger becomes ready to process a new command, i.e. shows its prompt void debuggerReady(); /// Emitted when a new command is added to the queue void commandAdded(); /// Emitted when real data from the program is received (needs improvement) void realDataReceived(QStringList); void stderrReceived(QStringList); private: IBreakpointController* m_breakpointController; IVariableController* m_variableController; IFrameStackModel* m_frameStackModel; KProcess* m_debuggerProcess; IDebugSession::DebuggerState m_state; QByteArray m_buffer; QStringList m_program; QList m_commandQueue; const QUrl& m_workingDirectory; + const QString m_envProfileName; private: /// objects to notify next QPointer m_nextNotifyObject; const char* m_nextNotifyMethod; /// whether the process is busy processing an internal command bool m_processBusy; /** * @brief Set the object to notify when the next command is done processing **/ void setNotifyNext(QPointer object, const char* method); /** * @brief Invoke the method given by setNotifyNext, and clear it **/ void notifyNext(); /** * @brief Process the next command in the queue. * WARNING: The queue must be non-empty when this is called. * If the process is busy doing something else, returns and does nothing. **/ void processNextCommand(); /** * @brief Clear the data accumulated in m_buffer. **/ void clearOutputBuffer(); /// stores whether the data currently received comes from the debugger /// or the debuggee. int m_inDebuggerData; }; /** * @brief Base class for all Pdb command objects. Those are enqueued in the debug session. **/ struct PdbCommand { public: /// notifyMethod must have a QByteArray argument, which is the /// output produced by the command. PdbCommand(QObject* notifyObject, const char* notifyMethod) : m_notifyObject(notifyObject) , m_notifyMethod(notifyMethod) , m_output(QByteArray()) {}; /** * @brief Implement this method in your sub-class to execute the command in the given session. * WARNING: The process is already locked and ready when this is called. * Don't acquire or release any locks or do fancy checking here, just do your business (write data * to the process, ...). Everything else is handled from outside. * @param session the debug session to run the command in **/ virtual void run(DebugSession* session) = 0; virtual ~PdbCommand() {}; void setOutput(QByteArray output) { m_output = output; }; QPointer notifyObject() { return m_notifyObject; }; const char* notifyMethod() { return m_notifyMethod; }; enum Type { InvalidType, InternalType, UserType }; inline Type type() const { return m_type; }; protected: Type m_type; QPointer m_notifyObject; const char* m_notifyMethod; QByteArray m_output; }; /** * @brief Base-class for commands which just write a simple piece of text to the debugger command line and read its output. **/ struct SimplePdbCommand : public PdbCommand { public: SimplePdbCommand(QObject* notifyObject, const char* notifyMethod, const QString& command) : PdbCommand(notifyObject, notifyMethod) , m_command(command) { m_type = InvalidType; }; void run(DebugSession* session) override { Q_ASSERT(m_command.endsWith('\n') && "command must end with a newline"); qCDebug(KDEV_PYTHON_DEBUGGER) << "running command:" << m_command<< m_notifyMethod; session->write(m_command.toUtf8()); } private: QString m_command; }; /** * @brief Represents a command which is invoked by kdevelop to obtain information to display in the UI. **/ struct InternalPdbCommand : public SimplePdbCommand { public: InternalPdbCommand(QObject* notifyObject, const char* notifyMethod, const QString& command) : SimplePdbCommand(notifyObject, notifyMethod, command) { m_type = InternalType; } ; }; /** * @brief Represents a command which is invoked by the user by clicking a button. **/ struct UserPdbCommand : public SimplePdbCommand { public: UserPdbCommand(QObject* notifyObject, const char* notifyMethod, const QString& command) : SimplePdbCommand(notifyObject, notifyMethod, command) { m_type = UserType; } ; }; } #endif // DEBUGSESSION_H diff --git a/debugger/pdblauncher.cpp b/debugger/pdblauncher.cpp index 7536b9a9..9e7c092d 100644 --- a/debugger/pdblauncher.cpp +++ b/debugger/pdblauncher.cpp @@ -1,131 +1,144 @@ /* This file is part of kdev-python, the python language plugin for KDevelop Copyright (C) 2012 Sven Brauch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "pdblauncher.h" #include #include "debugjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debuggerdebug.h" +#include namespace Python { PdbLauncher::PdbLauncher() { } QList< KDevelop::LaunchConfigurationPageFactory* > PdbLauncher::configPages() const { return QList(); } QString PdbLauncher::description() const { return i18n("A plugin to debug Python applications with pdb."); } QString PdbLauncher::id() { return "pdbdebugger"; } QString PdbLauncher::name() const { return "pdbdebugger"; } KJob* PdbLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { qCDebug(KDEV_PYTHON_DEBUGGER) << "start of debugger process requested"; if ( launchMode == "debug" ) { IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController() ->pluginForExtension("org.kdevelop.IExecuteScriptPlugin")->extension(); Q_ASSERT(iface); QString err; QString interpreter = iface->interpreter(cfg, err); // check the interpreter QProcess p; p.setReadChannelMode(QProcess::MergedChannels); p.start(interpreter, QStringList() << "--version"); p.waitForFinished(500); QByteArray version = p.readAll(); qCDebug(KDEV_PYTHON_DEBUGGER) << "interpreter version:" << version; if ( ! version.startsWith("Python 3.") ) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Sorry, debugging is only supported for Python 3.x applications."), i18n("Unsupported interpreter")); return nullptr; } QUrl scriptUrl; if ( iface->runCurrentFile(cfg) ) { auto document = KDevelop::ICore::self()->documentController()->activeDocument(); if ( ! document ) { qCDebug(KDEV_PYTHON_DEBUGGER) << "no current document"; return nullptr; } scriptUrl = document->url(); } else { scriptUrl = iface->script(cfg, err); } auto wd = iface->workingDirectory(cfg); if( !wd.isValid() || wd.isEmpty() ) { wd = QUrl::fromLocalFile( QFileInfo( scriptUrl.toLocalFile() ).absolutePath() ); } DebugJob* job = new DebugJob(); job->m_scriptUrl = scriptUrl; job->m_interpreter = interpreter; job->m_args = iface->arguments(cfg, err); job->m_workingDirectory = wd; + + const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig()); + QString envProfileName = iface->environmentProfileName(cfg); + + if (envProfileName.isEmpty()) { + qCWarning(KDEV_PYTHON_DEBUGGER) << "No environment profile specified, looks like a broken " + "configuration, please check run configuration " << cfg->name() << + ". Using default environment profile."; + envProfileName = environmentProfiles.defaultProfileName(); + } + job->m_envProfileName = envProfileName; + QList l; l << job; return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qCDebug(KDEV_PYTHON_DEBUGGER) << "unknown launch mode"; return nullptr; } QStringList PdbLauncher::supportedModes() const { return QStringList() << "debug"; } }