diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index 2f184dc7fc..e8963717ee 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,530 +1,533 @@ /* This file is part of KDevelop Copyright 2012 Ivan Shapovalov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputexecutejob.h" #include "outputmodel.h" #include "outputdelegate.h" #include "debug.h" #include #include #include #include #include #include #include namespace KDevelop { class OutputExecuteJobPrivate { public: OutputExecuteJobPrivate( KDevelop::OutputExecuteJob* owner ); void childProcessStdout(); void childProcessStderr(); void emitProgress(const IFilterStrategy::Progress& progress); QString joinCommandLine() const; QString getJobName(); template< typename T > static void mergeEnvironment( QProcessEnvironment& dest, const T& src ); QProcessEnvironment effectiveEnvironment() const; QStringList effectiveCommandLine() const; OutputExecuteJob* m_owner; KProcess* m_process; ProcessLineMaker* m_lineMaker; OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; QString m_environmentProfile; QHash m_environmentOverrides; QString m_jobName; bool m_outputStarted; }; OutputExecuteJobPrivate::OutputExecuteJobPrivate( OutputExecuteJob* owner ) : m_owner( owner ), m_process( new KProcess( m_owner ) ), m_lineMaker( new ProcessLineMaker( m_owner ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( OutputExecuteJob::JobNotStarted ), m_properties( OutputExecuteJob::DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_outputStarted( false ) { + KDevelop::restoreSystemEnvironment(m_process); } OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), d( new OutputExecuteJobPrivate( this ) ) { d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); connect( d->m_process, static_cast(&KProcess::finished), this, &OutputExecuteJob::childProcessExited ); connect( d->m_process, static_cast(&KProcess::error), this, &OutputExecuteJob::childProcessError ); connect( d->m_process, &KProcess::readyReadStandardOutput, this, [=] { d->childProcessStdout(); } ); connect( d->m_process, &KProcess::readyReadStandardError, this, [=] { d->childProcessStderr(); } ); } OutputExecuteJob::~OutputExecuteJob() { if( d->m_process->state() != QProcess::NotRunning ) { doKill(); } Q_ASSERT( d->m_process->state() == QProcess::NotRunning ); delete d; } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return d->m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return d->m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { d->m_arguments << argument; return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { d->m_arguments << arguments; return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return d->m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { d->m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { d->m_jobName = name; QString jobName = d->getJobName(); setObjectName( jobName ); setTitle( jobName ); } QUrl OutputExecuteJob::workingDirectory() const { return d->m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const QUrl& url ) { d->m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( d->m_status == JobNotStarted ); d->m_status = JobRunning; const bool isBuilder = d->m_properties.testFlag( IsBuilderHint ); const QUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( d->m_properties.testFlag( NeedWorkingDirectory ) ) { // A directory is not given, but we need it. setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "No build directory specified for a builder job." ) ); } else { setErrorText( i18n( "No working directory specified for a process." ) ); } return emitResult(); } setModel( new OutputModel ); } else { // Basic sanity checks. if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } QFileInfo workingDirInfo( effectiveWorkingDirectory.toLocalFile() ); if( !workingDirInfo.isDir() ) { // If a working directory does not actually exist, either bail out or create it empty, // depending on what we need by properties. // We use a dedicated bool variable since !isDir() may also mean that it exists, // but is not a directory, or a symlink to an inexistent object. bool successfullyCreated = false; if( !d->m_properties.testFlag( CheckWorkingDirectory ) ) { successfullyCreated = QDir().mkdir( effectiveWorkingDirectory.toLocalFile() ); } if( !successfullyCreated ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } else { setErrorText( i18n( "Working directory '%1' does not exist or is not a directory", effectiveWorkingDirectory.toDisplayString(QUrl::PreferLocalFile) ) ); } return emitResult(); } } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); if (d->m_filteringStrategyPtr) { model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); } else { model()->setFilteringStrategy(d->m_filteringStrategy); } setDelegate( new OutputDelegate ); connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { d->emitProgress(progress); }); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &OutputExecuteJob::postProcessStdout ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &OutputExecuteJob::postProcessStderr ); } else { connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, model(), &OutputModel::appendLines ); } if( !d->m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { d->m_outputStarted = true; startOutput(); } const QString joinedCommandLine = d->joinCommandLine(); QString headerLine; if( !effectiveWorkingDirectory.isEmpty() ) { headerLine = effectiveWorkingDirectory.toString( QUrl::PreferLocalFile | QUrl::StripTrailingSlash ) + "> " + joinedCommandLine; } else { headerLine = joinedCommandLine; } model()->appendLine( headerLine ); if( !effectiveWorkingDirectory.isEmpty() ) { d->m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } d->m_process->setProcessEnvironment( d->effectiveEnvironment() ); + KDevelop::restoreSystemEnvironment(d->m_process); if (!d->effectiveCommandLine().isEmpty()) { d->m_process->setProgram( d->effectiveCommandLine() ); // there is no way to input data in the output view so redirect stdin to the null device d->m_process->setStandardInputFile(QProcess::nullDevice()); qCDebug(OUTPUTVIEW) << "Starting:" << d->m_process->program().join(QStringLiteral(" ")) << "in" << d->m_process->workingDirectory(); d->m_process->start(); } else { QString errorMessage = i18n("Failed to specify program to start"); model()->appendLine( i18n( "*** %1 ***", errorMessage) ); setErrorText(errorMessage); setError( FailedShownError ); emitResult(); return; } } bool OutputExecuteJob::doKill() { const int terminateKillTimeout = 1000; // msecs if( d->m_status != JobRunning ) return true; d->m_status = JobCanceled; d->m_process->terminate(); bool terminated = d->m_process->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->m_process->kill(); terminated = d->m_process->waitForFinished( terminateKillTimeout ); } d->m_lineMaker->flushBuffers(); if( terminated ) { model()->appendLine( i18n( "*** Killed process ***" ) ); } else { // It survived SIGKILL, leave it alone... model()->appendLine( i18n( "*** Warning: could not kill the process ***" ) ); } return true; } void OutputExecuteJob::childProcessError( QProcess::ProcessError processError ) { // This can be called twice: one time via an error() signal, and second - from childProcessExited(). // Avoid doing things in second time. if( d->m_status != OutputExecuteJob::JobRunning ) return; d->m_status = OutputExecuteJob::JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("%1 has failed to start", commandLine().at(0)); break; case QProcess::Crashed: errorValue = i18n("%1 has crashed", commandLine().at(0)); break; case QProcess::ReadError: errorValue = i18n("Read error"); break; case QProcess::WriteError: errorValue = i18n("Write error"); break; case QProcess::Timedout: errorValue = i18n("Waiting for the process has timed out"); break; default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", d->m_process->exitCode()); break; } // Show the toolview if it's hidden for the user to be able to diagnose errors. if( !d->m_outputStarted ) { d->m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( d->m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { d->m_status = JobSucceeded; d->m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJobPrivate::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( OutputExecuteJob::DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJobPrivate::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( OutputExecuteJob::DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) { if (progress.percent != -1) { m_owner->emitPercent(progress.percent, 100); } if (!progress.status.isEmpty()) { m_owner->infoMessage(m_owner, progress.status); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; // clear the other d->m_filteringStrategyPtr.reset(nullptr); } void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) { d->m_filteringStrategyPtr.reset(filterStrategy); // clear the other d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return d->m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { d->m_properties = properties; } else { d->m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { d->m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return d->m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { d->m_environmentProfile = profile; + KDevelop::restoreSystemEnvironment(d->m_process); } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { d->m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { d->m_environmentOverrides.remove( name ); } template< typename T > void OutputExecuteJobPrivate::mergeEnvironment( QProcessEnvironment& dest, const T& src ) { for( typename T::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJobPrivate::effectiveEnvironment() const { const EnvironmentGroupList environmentGroup( KSharedConfig::openConfig() ); QString environmentProfile = m_owner->environmentProfile(); if( environmentProfile.isEmpty() ) { environmentProfile = environmentGroup.defaultGroup(); } QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); auto userEnv = environmentGroup.variables(environmentProfile); expandVariables(userEnv, environment); OutputExecuteJobPrivate::mergeEnvironment( environment, userEnv ); OutputExecuteJobPrivate::mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( OutputExecuteJob::PortableMessages ) ) { environment.remove( QStringLiteral( "LC_ALL" ) ); environment.insert( QStringLiteral( "LC_MESSAGES" ), QStringLiteral( "C" ) ); } return environment; } QString OutputExecuteJobPrivate::joinCommandLine() const { return KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJobPrivate::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = m_owner->privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << m_owner->privilegedExecutionCommand() << QStringLiteral("--") << m_owner->commandLine(); } else { return m_owner->commandLine(); } } QString OutputExecuteJobPrivate::getJobName() { const QString joinedCommandLine = joinCommandLine(); if( m_properties.testFlag( OutputExecuteJob::AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { return m_jobName + ": " + joinedCommandLine; } else { return joinedCommandLine; } } else { return m_jobName; } } } // namespace KDevelop #include "moc_outputexecutejob.cpp" diff --git a/outputview/outputjob.cpp b/outputview/outputjob.cpp index 6a6d1d6967..ee559e36b9 100644 --- a/outputview/outputjob.cpp +++ b/outputview/outputjob.cpp @@ -1,183 +1,184 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputjob.h" #include #include +#include #include "interfaces/icore.h" #include "interfaces/iplugincontroller.h" #include "outputview/ioutputview.h" using namespace KDevelop; OutputJob::OutputJob(QObject* parent, OutputJobVerbosity verbosity) : KJob(parent) , m_standardToolView(-1) , m_type(IOutputView::OneView) , m_behaviours(IOutputView::AllowUserClose) , m_killJobOnOutputClose(true) , m_verbosity(verbosity) , m_outputId(-1) , m_outputDelegate(0) { } void OutputJob::startOutput() { IPlugin* i = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { int tvid; if (m_standardToolView != -1) { tvid = view->standardToolView( static_cast(m_standardToolView) ); } else { tvid = view->registerToolView(m_toolTitle, m_type, m_toolIcon); } if (m_title.isEmpty()) m_title = objectName(); m_outputId = view->registerOutputInToolView( tvid, m_title, m_behaviours ); if (!m_outputModel) { m_outputModel = new QStandardItemModel(0); } // Keep the item model around after the job is gone view->setModel(m_outputId, m_outputModel); if (!m_outputDelegate) { m_outputDelegate = new QItemDelegate(0); } view->setDelegate(m_outputId, m_outputDelegate); if (m_killJobOnOutputClose) { // can't use qt5 signal slot syntax here, IOutputView is no a QObject connect(i, SIGNAL(outputRemoved(int,int)), this, SLOT(outputViewRemoved(int,int))); } if (m_verbosity == OutputJob::Verbose) view->raiseOutput(m_outputId); } } } void OutputJob::outputViewRemoved(int toolViewId, int id) { Q_UNUSED(toolViewId); if (id == m_outputId && m_killJobOnOutputClose) { // Make sure that the job emits result signal as the job // might be used in composite jobs and that one depends // on result being emitted to know whether a subjob // is done. kill( KJob::EmitResult ); } } void KDevelop::OutputJob::setTitle(const QString & title) { m_title = title; if (m_outputId >= 0 && m_standardToolView >= 0) { IPlugin* i = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IOutputView")); if( i ) { KDevelop::IOutputView* view = i->extension(); if( view ) { view->setTitle(m_outputId, title); } } } } void KDevelop::OutputJob::setViewType(IOutputView::ViewType type) { m_type = type; } void KDevelop::OutputJob::setBehaviours(IOutputView::Behaviours behaviours) { m_behaviours = behaviours; } void KDevelop::OutputJob::setKillJobOnOutputClose(bool killJobOnOutputClose) { m_killJobOnOutputClose = killJobOnOutputClose; } void KDevelop::OutputJob::setModel(QAbstractItemModel * model) { if (m_outputModel) { delete m_outputModel; } m_outputModel = model; if (m_outputModel) { m_outputModel->setParent(this); } } void KDevelop::OutputJob::setDelegate(QAbstractItemDelegate * delegate) { m_outputDelegate = delegate; } QAbstractItemModel * KDevelop::OutputJob::model() const { return m_outputModel; } void KDevelop::OutputJob::setStandardToolView(IOutputView::StandardToolView standard) { m_standardToolView = standard; } void OutputJob::setToolTitle(const QString& title) { m_toolTitle = title; } void OutputJob::setToolIcon(const QIcon& icon) { m_toolIcon = icon; } int OutputJob::outputId() const { return m_outputId; } OutputJob::OutputJobVerbosity OutputJob::verbosity() const { return m_verbosity; } void OutputJob::setVerbosity(OutputJob::OutputJobVerbosity verbosity) { m_verbosity = verbosity; } diff --git a/outputview/outputjob.h b/outputview/outputjob.h index 5c9524c3a4..33b11bae57 100644 --- a/outputview/outputjob.h +++ b/outputview/outputjob.h @@ -1,109 +1,110 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_OUTPUTJOB_H #define KDEVPLATFORM_OUTPUTJOB_H #include #include #include #include #include class QStandardItemModel; class QItemDelegate; +class KProcess; namespace KDevelop { class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputJob : public KJob { Q_OBJECT public: enum { FailedShownError = UserDefinedError + 100 //job failed and failure is shown in OutputView }; enum OutputJobVerbosity { Silent, Verbose }; OutputJob(QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose); void startOutput(); OutputJobVerbosity verbosity() const; void setVerbosity(OutputJobVerbosity verbosity); QAbstractItemModel* model() const; /// Set the \a title for this job's output tab. If not set, will default to the job's objectName(). void setTitle(const QString& title); protected: void setStandardToolView(IOutputView::StandardToolView standard); void setToolTitle(const QString& title); void setToolIcon(const QIcon& icon); void setViewType(IOutputView::ViewType type); void setBehaviours(IOutputView::Behaviours behaviours); void setKillJobOnOutputClose(bool killJobOnOutputClose); /** * Sets the model for the view that shows this jobs output. * * The view takes ownership of the model, but it is safe to * use the model while the job is running. * * NOTE: Do not reuse the same model for different jobs. */ void setModel(QAbstractItemModel* model); /** * Sets the delegate for the view that shows this jobs output. * * The view takes ownership of the delegate, but it is safe to * use the delegate while the job is running. * * NOTE: Do not reuse the same delegate for different jobs. */ void setDelegate(QAbstractItemDelegate* delegate); int outputId() const; private Q_SLOTS: void outputViewRemoved(int , int id); private: int m_standardToolView; QString m_title, m_toolTitle; QIcon m_toolIcon; IOutputView::ViewType m_type; IOutputView::Behaviours m_behaviours; bool m_killJobOnOutputClose; OutputJobVerbosity m_verbosity; int m_outputId; QPointer m_outputModel; QAbstractItemDelegate* m_outputDelegate; }; } #endif diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp index 20d8acdb56..1842d8c482 100644 --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -1,252 +1,252 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappjob.h" #include "executescriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iexecutescriptplugin.h" #include "debug.h" using namespace KDevelop; ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(new KProcess( this )), lineMaker(new KDevelop::ProcessLineMaker( proc, this )) { qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job"; setCapabilities(Killable); IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecuteScriptPlugin"))->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; QString interpreterString = iface->interpreter( cfg, err ); // check for errors happens in the executescript plugin already KShell::Errors err_; QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ); if ( interpreter.isEmpty() ) { // This should not happen, because of the checks done in the executescript plugin qWarning() << "no interpreter specified"; return; } if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } QUrl script; if( !iface->runCurrentFile( cfg ) ) { script = iface->script( cfg, err ); } else { KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument(); if( !document ) { setError( -1 ); setErrorText( i18n( "There is no active document to launch." ) ); return; } script = document->url(); } if( !err.isEmpty() ) { setError( -3 ); setErrorText( err ); return; } QString remoteHost = iface->remoteHost( cfg, err ); if( !err.isEmpty() ) { setError( -4 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } KDevelop::OutputModel::OutputFilterStrategy currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel; m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &ScriptAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &ScriptAppJob::processFinished ); // Now setup the process parameters - + KDevelop::restoreSystemEnvironment(proc); proc->setEnvironment( l.createEnvironment( envgrp, proc->systemEnvironment()) ); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program; if (!remoteHost.isEmpty()) { program << QStringLiteral("ssh"); QStringList parts = remoteHost.split(QLatin1Char(':')); program << parts.first(); if (parts.length() > 1) { program << "-p "+parts.at(1); } } program << interpreter; program << script.toLocalFile(); program << arguments; qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program; proc->setOutputChannelMode(KProcess::MergedChannels); proc->setProgram( program ); setTitle(cfg->name()); } void ScriptAppJob::start() { qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(QLatin1Char( ' ' ) ) ) ); proc->start(); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ScriptAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done"; emitResult(); } void ScriptAppJob::processError( QProcess::ProcessError error ) { qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString(); if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(QLatin1Char( ' ' ) ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error"; } void ScriptAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* ScriptAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/externalscript/externalscriptjob.cpp b/plugins/externalscript/externalscriptjob.cpp index 5b48c2bfbd..aee16f07ad 100644 --- a/plugins/externalscript/externalscriptjob.cpp +++ b/plugins/externalscript/externalscriptjob.cpp @@ -1,399 +1,401 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "externalscriptjob.h" #include "externalscriptitem.h" #include "externalscriptdebug.h" #include "externalscriptplugin.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include using namespace KDevelop; ExternalScriptJob::ExternalScriptJob( ExternalScriptItem* item, const QUrl& url, ExternalScriptPlugin* parent ) : KDevelop::OutputJob( parent ), m_proc( 0 ), m_lineMaker( 0 ), m_outputMode( item->outputMode() ), m_inputMode( item->inputMode() ), m_errorMode( item->errorMode() ), m_filterMode( item->filterMode() ), m_document( 0 ), m_url( url ), m_selectionRange( KTextEditor::Range::invalid() ), m_showOutput( item->showOutput() ) { qCDebug(PLUGIN_EXTERNALSCRIPT) << "creating external script job"; setCapabilities( Killable ); setStandardToolView( KDevelop::IOutputView::RunView ); setBehaviours( KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll ); KDevelop::OutputModel* model = new KDevelop::OutputModel; model->setFilteringStrategy(static_cast(m_filterMode)); setModel( model ); setDelegate( new KDevelop::OutputDelegate ); // also merge when error mode "equals" output mode if ( (m_outputMode == ExternalScriptItem::OutputInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorInsertAtCursor) || (m_outputMode == ExternalScriptItem::OutputReplaceDocument && m_errorMode == ExternalScriptItem::ErrorReplaceDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) || // also these two otherwise they clash... (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrDocument) || (m_outputMode == ExternalScriptItem::OutputReplaceSelectionOrDocument && m_errorMode == ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor) ) { m_errorMode = ExternalScriptItem::ErrorMergeOutput; } KTextEditor::View* view = KDevelop::ICore::self()->documentController()->activeTextDocumentView(); if ( m_outputMode != ExternalScriptItem::OutputNone || m_inputMode != ExternalScriptItem::InputNone || m_errorMode != ExternalScriptItem::ErrorNone ) { if ( !view ) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot run script '%1' since it tries to access " "the editor contents but no document is open.", item->text() ), i18n( "No Document Open" ) ); return; } m_document = view->document(); connect(m_document, &KTextEditor::Document::aboutToClose, this, [&] { kill(); }); m_selectionRange = view->selectionRange(); m_cursorPosition = view->cursorPosition(); } if ( item->saveMode() == ExternalScriptItem::SaveCurrentDocument && view ) { view->document()->save(); } else if ( item->saveMode() == ExternalScriptItem::SaveAllDocuments ) { foreach ( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { doc->save(); } } QString command = item->command(); QString workingDir = item->workingDirectory(); if(item->performParameterReplacement()) command.replace( QLatin1String("%i"), QString::number( QCoreApplication::applicationPid() ) ); if ( !m_url.isEmpty() ) { const QUrl url = m_url; KDevelop::ProjectFolderItem* folder = 0; if ( KDevelop::ICore::self()->projectController()->findProjectForUrl( url ) ) { QList folders = KDevelop::ICore::self()->projectController()->findProjectForUrl(url)->foldersForPath(KDevelop::IndexedString(url)); if ( !folders.isEmpty() ) { folder = folders.first(); } } if ( folder ) { if ( folder->path().isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = folder->path().toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%d"), KShell::quoteArg( m_url.toString(QUrl::PreferLocalFile) ) ); if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } else { if ( m_url.isLocalFile() && workingDir.isEmpty() ) { ///TODO: make configurable, use fallback to project dir workingDir = view->document()->url().adjusted(QUrl::RemoveFilename).toLocalFile(); } ///TODO: make those placeholders escapeable if(item->performParameterReplacement()) { command.replace( QLatin1String("%u"), KShell::quoteArg( m_url.toString() ) ); ///TODO: does that work with remote files? QFileInfo info( m_url.toString(QUrl::PreferLocalFile) ); command.replace( QLatin1String("%f"), KShell::quoteArg( info.filePath() ) ); command.replace( QLatin1String("%b"), KShell::quoteArg( info.baseName() ) ); command.replace( QLatin1String("%n"), KShell::quoteArg( info.fileName() ) ); command.replace( QLatin1String("%d"), KShell::quoteArg( info.path() ) ); if ( view->document() && view->selection() ) { command.replace( QLatin1String("%s"), KShell::quoteArg( view->selectionText() ) ); } if ( KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl( m_url ) ) { command.replace( QLatin1String("%p"), project->path().pathOrUrl() ); } } } } m_proc = new KProcess( this ); + KDevelop::restoreSystemEnvironment(m_proc); if ( !workingDir.isEmpty() ) { m_proc->setWorkingDirectory( workingDir ); } m_lineMaker = new ProcessLineMaker( m_proc, this ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &ExternalScriptJob::receivedStdoutLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, model, &OutputModel::appendLines ); connect( m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &ExternalScriptJob::receivedStderrLines ); connect( m_proc, static_cast(&KProcess::error), this, &ExternalScriptJob::processError ); connect( m_proc, static_cast(&KProcess::finished), this, &ExternalScriptJob::processFinished ); // Now setup the process parameters qCDebug(PLUGIN_EXTERNALSCRIPT) << "setting command:" << command; if ( m_errorMode == ExternalScriptItem::ErrorMergeOutput ) { m_proc->setOutputChannelMode( KProcess::MergedChannels ); } else { m_proc->setOutputChannelMode( KProcess::SeparateChannels ); } m_proc->setShellCommand( command ); setObjectName( command ); } void ExternalScriptJob::start() { qCDebug(PLUGIN_EXTERNALSCRIPT) << "launching?" << m_proc; if ( m_proc ) { if ( m_showOutput ) { startOutput(); } appendLine( i18n( "Running external script: %1", m_proc->program().join( QStringLiteral( " " ) ) ) ); m_proc->start(); if ( m_inputMode != ExternalScriptItem::InputNone ) { QString inputText; switch ( m_inputMode ) { case ExternalScriptItem::InputNone: // do nothing; break; case ExternalScriptItem::InputSelectionOrNone: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } // else nothing break; case ExternalScriptItem::InputSelectionOrDocument: if ( m_selectionRange.isValid() ) { inputText = m_document->text(m_selectionRange); } else { inputText = m_document->text(); } break; case ExternalScriptItem::InputDocument: inputText = m_document->text(); break; } ///TODO: what to do with the encoding here? /// maybe ask Christoph for what kate returns... m_proc->write( inputText.toUtf8() ); m_proc->closeWriteChannel(); } } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ExternalScriptJob::doKill() { if ( m_proc ) { m_proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ExternalScriptJob::processFinished( int exitCode , QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if ( exitCode == 0 && status == QProcess::NormalExit ) { if ( m_outputMode != ExternalScriptItem::OutputNone ) { if ( !m_stdout.isEmpty() ) { QString output = m_stdout.join( QStringLiteral("\n") ); switch ( m_outputMode ) { case ExternalScriptItem::OutputNone: // do nothing; break; case ExternalScriptItem::OutputCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::OutputInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::OutputReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::OutputReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::OutputReplaceDocument: m_document->setText( output ); break; } } } if ( m_errorMode != ExternalScriptItem::ErrorNone && m_errorMode != ExternalScriptItem::ErrorMergeOutput ) { QString output = m_stderr.join( QStringLiteral("\n") ); if ( !output.isEmpty() ) { switch ( m_errorMode ) { case ExternalScriptItem::ErrorNone: case ExternalScriptItem::ErrorMergeOutput: // do nothing; break; case ExternalScriptItem::ErrorCreateNewFile: KDevelop::ICore::self()->documentController()->openDocumentFromText( output ); break; case ExternalScriptItem::ErrorInsertAtCursor: m_document->insertText( m_cursorPosition, output ); break; case ExternalScriptItem::ErrorReplaceSelectionOrInsertAtCursor: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->insertText( m_cursorPosition, output ); } break; case ExternalScriptItem::ErrorReplaceSelectionOrDocument: if ( m_selectionRange.isValid() ) { m_document->replaceText( m_selectionRange, output ); } else { m_document->setText( output ); } break; case ExternalScriptItem::ErrorReplaceDocument: m_document->setText( output ); break; } } } appendLine( i18n( "*** Exited normally ***" ) ); } else { if ( status == QProcess::NormalExit ) appendLine( i18n( "*** Exited with return code: %1 ***", QString::number( exitCode ) ) ); else if ( error() == KJob::KilledJobError ) appendLine( i18n( "*** Process aborted ***" ) ); else appendLine( i18n( "*** Crashed with return code: %1 ***", QString::number( exitCode ) ) ); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process done"; emitResult(); } void ExternalScriptJob::processError( QProcess::ProcessError error ) { if ( error == QProcess::FailedToStart ) { setError( -1 ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", m_proc->program().join( QLatin1Char(' ') ) ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXTERNALSCRIPT) << "Process error"; } void ExternalScriptJob::appendLine( const QString& l ) { if ( KDevelop::OutputModel* m = model() ) { m->appendLine( l ); } } KDevelop::OutputModel* ExternalScriptJob::model() { return dynamic_cast( OutputJob::model() ); } void ExternalScriptJob::receivedStderrLines(const QStringList& lines) { m_stderr += lines; } void ExternalScriptJob::receivedStdoutLines(const QStringList& lines) { m_stdout += lines; } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/util/commandexecutor.cpp b/util/commandexecutor.cpp index 578a6bf48f..1003c2d756 100644 --- a/util/commandexecutor.cpp +++ b/util/commandexecutor.cpp @@ -1,167 +1,171 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "commandexecutor.h" #include "processlinemaker.h" + +#include + #include #include #include #include #include namespace KDevelop { class CommandExecutorPrivate { public: CommandExecutorPrivate( CommandExecutor* cmd ) : m_exec(cmd), m_useShell(false) { } CommandExecutor* m_exec; KProcess* m_process; ProcessLineMaker* m_lineMaker; QString m_command; QStringList m_args; QString m_workDir; QMap m_env; bool m_useShell; void procError( QProcess::ProcessError error ) { Q_UNUSED(error) m_lineMaker->flushBuffers(); emit m_exec->failed( error ); } void procFinished( int code, QProcess::ExitStatus status ) { m_lineMaker->flushBuffers(); if( status == QProcess::NormalExit ) emit m_exec->completed(code); } }; CommandExecutor::CommandExecutor( const QString& command, QObject* parent ) : QObject(parent), d(new CommandExecutorPrivate(this)) { d->m_process = new KProcess(this); + KDevelop::restoreSystemEnvironment(d->m_process); d->m_process->setOutputChannelMode( KProcess::SeparateChannels ); d->m_lineMaker = new ProcessLineMaker( d->m_process ); d->m_command = command; connect( d->m_lineMaker, &ProcessLineMaker::receivedStdoutLines, this, &CommandExecutor::receivedStandardOutput ); connect( d->m_lineMaker, &ProcessLineMaker::receivedStderrLines, this, &CommandExecutor::receivedStandardError ); connect( d->m_process, static_cast(&KProcess::error), this, [&] (QProcess::ProcessError error) { d->procError(error); } ); connect( d->m_process, static_cast(&KProcess::finished), this, [&] (int code, QProcess::ExitStatus status) { d->procFinished(code, status); } ); } CommandExecutor::~CommandExecutor() { delete d->m_process; delete d->m_lineMaker; delete d; } void CommandExecutor::setEnvironment( const QMap& env ) { d->m_env = env; } void CommandExecutor::setEnvironment( const QStringList& env ) { QMap envmap; foreach( const QString& var, env ) { int sep = var.indexOf( '=' ); envmap.insert( var.left( sep ), var.mid( sep + 1 ) ); } d->m_env = envmap; } void CommandExecutor::setArguments( const QStringList& args ) { d->m_args = args; } void CommandExecutor::setWorkingDirectory( const QString& dir ) { d->m_workDir = dir; } bool CommandExecutor::useShell() const { return d->m_useShell; } void CommandExecutor::setUseShell( bool shell ) { d->m_useShell = shell; } void CommandExecutor::start() { for(auto it = d->m_env.constBegin(), itEnd = d->m_env.constEnd(); it!=itEnd; ++it) { d->m_process->setEnv( it.key(), it.value() ); } d->m_process->setWorkingDirectory( d->m_workDir ); if( !d->m_useShell ) { d->m_process->setProgram( d->m_command, d->m_args ); } else { QStringList arguments; Q_FOREACH( const QString &a, d->m_args ) arguments << KShell::quoteArg( a ); d->m_process->setShellCommand(d->m_command + ' ' + arguments.join(QLatin1Char(' '))); } d->m_process->start(); } void CommandExecutor::setCommand( const QString& command ) { d->m_command = command; } void CommandExecutor::kill() { d->m_process->kill(); } QString CommandExecutor::command() const { return d->m_command; } QStringList CommandExecutor::arguments() const { return d->m_args; } QString CommandExecutor::workingDirectory() const { return d->m_workDir; } } #include "moc_commandexecutor.cpp" diff --git a/util/environmentgrouplist.cpp b/util/environmentgrouplist.cpp index c762296517..08509e501f 100644 --- a/util/environmentgrouplist.cpp +++ b/util/environmentgrouplist.cpp @@ -1,225 +1,244 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentgrouplist.h" #include #include #include +#include #include +#include #include #include namespace KDevelop { class EnvironmentGroupListPrivate { public: QMap > m_groups; QString m_defaultGroup; }; } using namespace KDevelop; namespace { namespace Strings { inline QString defaultEnvGroupKey() { return QStringLiteral("Default Environment Group"); } inline QString envGroup() { return QStringLiteral("Environment Settings"); } inline QString groupListKey() { return QStringLiteral("Group List"); } inline QString defaultGroup() { return QStringLiteral("default"); } } void decode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); d->m_defaultGroup = cfg.readEntry( Strings::defaultEnvGroupKey(), Strings::defaultGroup() ); QStringList grouplist = cfg.readEntry( Strings::groupListKey(), QStringList{Strings::defaultGroup()} ); foreach( const QString &envgrpname, grouplist ) { KConfigGroup envgrp( &cfg, envgrpname ); QMap variables; foreach( const QString &varname, envgrp.keyList() ) { variables[varname] = envgrp.readEntry( varname, QString() ); } d->m_groups.insert( envgrpname, variables ); } } void encode( KConfig* config, EnvironmentGroupListPrivate* d ) { KConfigGroup cfg( config, Strings::envGroup() ); cfg.writeEntry( Strings::defaultEnvGroupKey(), d->m_defaultGroup ); cfg.writeEntry( Strings::groupListKey(), d->m_groups.keys() ); foreach( const QString &group, cfg.groupList() ) { if( !d->m_groups.contains( group ) ) { cfg.deleteGroup( group ); } } for(auto it = d->m_groups.cbegin(), itEnd = d->m_groups.cend(); it!=itEnd; ++it) { KConfigGroup envgrp( &cfg, it.key() ); envgrp.deleteGroup(); const auto val = it.value(); for(auto it2 = val.cbegin(), it2End = val.cend(); it2!=it2End; ++it2) { envgrp.writeEntry( it2.key(), *it2 ); } } cfg.sync(); } } EnvironmentGroupList::EnvironmentGroupList( const EnvironmentGroupList& rhs ) : d( new EnvironmentGroupListPrivate( *rhs.d ) ) { } EnvironmentGroupList& EnvironmentGroupList::operator=( const EnvironmentGroupList& rhs ) { *d = *rhs.d; return *this; } EnvironmentGroupList::EnvironmentGroupList( KSharedConfigPtr config ) : d( new EnvironmentGroupListPrivate ) { decode( config.data(), d ); } EnvironmentGroupList::EnvironmentGroupList( KConfig* config ) : d( new EnvironmentGroupListPrivate ) { decode( config, d ); } EnvironmentGroupList::~EnvironmentGroupList() { delete d; } const QMap EnvironmentGroupList::variables( const QString& group ) const { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QMap& EnvironmentGroupList::variables( const QString& group ) { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QString EnvironmentGroupList::defaultGroup() const { return d->m_defaultGroup; } void EnvironmentGroupList::setDefaultGroup( const QString& group ) { if( group.isEmpty() ) { return; } if( d->m_groups.contains( group ) ) { d->m_defaultGroup = group; } } void EnvironmentGroupList::saveSettings( KConfig* config ) const { encode( config, d ); config->sync(); } void EnvironmentGroupList::loadSettings( KConfig* config ) { d->m_groups.clear(); decode( config, d ); } QStringList EnvironmentGroupList::groups() const { return d->m_groups.keys(); } void EnvironmentGroupList::removeGroup( const QString& group ) { d->m_groups.remove( group ); } EnvironmentGroupList::EnvironmentGroupList() : d( new EnvironmentGroupListPrivate ) { } QStringList EnvironmentGroupList::createEnvironment( const QString & group, const QStringList & defaultEnvironment ) const { QMap retMap; foreach( const QString &line, defaultEnvironment ) { QString varName = line.section( '=', 0, 0 ); QString varValue = line.section( '=', 1 ); retMap.insert( varName, varValue ); } if( !group.isEmpty() ) { QMap userMap = variables(group); for( QMap::const_iterator it = userMap.constBegin(); it != userMap.constEnd(); ++it ) { retMap.insert( it.key(), it.value() ); } } QStringList env; for( QMap::const_iterator it = retMap.constBegin(); it != retMap.constEnd(); ++it ) { env << it.key() + '=' + it.value(); } return env; } void KDevelop::expandVariables(QMap& variables, const QProcessEnvironment& environment) { QRegularExpression rVar(QStringLiteral("(?program() << shouldChange; + if ( shouldChange ) { + // running in AppImage + auto env = process->processEnvironment(); + env.insert("LD_LIBRARY_PATH", qgetenv("_KDEV_OLD_LD_LIBRARY_PATH")); + env.insert("QT_PLUGIN_PATH", qgetenv("_KDEV_OLD_QT_PLUGIN_PATH")); + env.insert("XDG_DATA_DIRS", qgetenv("_KDEV_OLD_XDG_DATA_DIRS")); + env.insert("PATH", qgetenv("_KDEV_OLD_PATH")); + env.insert("KDE_FORK_SLAVES", qgetenv("_KDEV_OLD_KDE_FORK_SLAVES")); + process->setProcessEnvironment(env); + } + qWarning() << "resulting environment:" << process->processEnvironment().toStringList(); +} diff --git a/util/environmentgrouplist.h b/util/environmentgrouplist.h index 9e26a1b55a..24dee048ca 100644 --- a/util/environmentgrouplist.h +++ b/util/environmentgrouplist.h @@ -1,148 +1,157 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #define KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #include "utilexport.h" #include class QProcessEnvironment; class KConfig; +class QProcess; template class QMap; class QString; class QStringList; namespace KDevelop { /** * This class manages a list of environment groups, each group containing a number * of environment variables and their values. * * The class is constructed from a KConfig object for easy usage in the plugins. * * The methods to change the environments is protected to disallow access to those methods * from plugins, only the environment widget is allowed to change them. * * Example Usage * \code * KSharedConfigPtr config = KSharedConfig::openConfig(); * EnvironmentGroupList env(config); * KConfigGroup cfg(config, "QMake Builder"); * QMap myenvVars = env.variables( cfg.readEntry("QMake Environment") ); * \endcode * * Two entries are used by this class: * "Default Environment Group" and "Environment Variables". * * "Default Environment Variables" stores the default group that should be used if the * user didn't select a group via a plugins configuration dialog. * * "Environment Variables" entry stores the actual list of * . The groupname can't contain '%' or '='. * For example, suppose that two configuration, say "release" and "debug" exist. * Then the actual contents of .kdev4 project file will be * * \code * [Environment Settings] * Default Environment Group=debug * Environment Variables=debug_PATH=/home/kde-devel/usr/bin,release_PATH=/usr/bin * \endcode * */ class KDEVPLATFORMUTIL_EXPORT EnvironmentGroupList { public: EnvironmentGroupList( const EnvironmentGroupList& rhs ); EnvironmentGroupList& operator=( const EnvironmentGroupList& rhs ); /** * Creates an a list of EnvironmentGroups from a KConfig object * @param config the KConfig object to read the environment groups from */ explicit EnvironmentGroupList( KSharedConfigPtr config ); explicit EnvironmentGroupList( KConfig* config ); ~EnvironmentGroupList(); /** * Creates a merged environment between the defaults specified by * \a defaultEnvironment and those saved in \a group */ QStringList createEnvironment(const QString& group, const QStringList& defaultEnvironment ) const; /** * returns the variables that are set for a given group. * This function provides read-only access to the environment * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ const QMap variables( const QString& group ) const; /** * returns the default group * The default group should be used by plugins unless the user chooses a different group * @return the name of the default group, defaults to "default" */ QString defaultGroup() const; /** * Fetch the list of known groups from the list * @return the list of groups */ QStringList groups() const; protected: EnvironmentGroupList(); /** * returns the variables that are set for a given group. * This function provides write access to the environment, so new variables can be inserted, existing ones changed or deleted * * If a non-existing group is specified this returns a new empty map and that way this function can be used to add a new group * to the list of environment groups * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ QMap& variables( const QString& group ); /** * Changes the default group. * @param group a new groupname, if a group of this name doesn't exist the default group is not changed */ void setDefaultGroup( const QString& group ); /** * Stores the environment groups in this list to the given KConfig object * @param config a KConfig object to which the environment settings should be stored */ void saveSettings( KConfig* config ) const; void loadSettings( KConfig* config ); void removeGroup( const QString& group ); private: class EnvironmentGroupListPrivate* const d; }; +/** + * When running in an AppImage environment, external processes + * should not inherit our environment variables, esp. LD_LIBRARY_PATH. + * This function restores the original system's environment for the + * given @p process. + */ +KDEVPLATFORMUTIL_EXPORT void restoreSystemEnvironment(QProcess* process); + KDEVPLATFORMUTIL_EXPORT void expandVariables(QMap& variables, const QProcessEnvironment& environment); } #endif diff --git a/vcs/dvcs/dvcsjob.cpp b/vcs/dvcs/dvcsjob.cpp index a45d87ce34..bf395b7d53 100644 --- a/vcs/dvcs/dvcsjob.cpp +++ b/vcs/dvcs/dvcsjob.cpp @@ -1,333 +1,336 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2002-2003 Christian Loose * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS * * Copyright 2008 Evgeniy Ivanov * * Copyright Aleix Pol Gonzalez * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 "dvcsjob.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include +#include + using namespace KDevelop; struct DVcsJobPrivate { DVcsJobPrivate() : childproc(new KProcess), vcsplugin(0), ignoreError(false) {} ~DVcsJobPrivate() { delete childproc; } KProcess* childproc; VcsJob::JobStatus status; QByteArray output; QByteArray errorOutput; IPlugin* vcsplugin; QVariant results; OutputModel* model; bool ignoreError; }; DVcsJob::DVcsJob(const QDir& workingDir, IPlugin* parent, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity), d(new DVcsJobPrivate) { Q_ASSERT(workingDir.exists()); + KDevelop::restoreSystemEnvironment(d->childproc); d->status = JobNotStarted; d->vcsplugin = parent; d->childproc->setWorkingDirectory(workingDir.absolutePath()); d->model = new OutputModel; d->ignoreError = false; setModel(d->model); setCapabilities(Killable); connect(d->childproc, static_cast(&KProcess::finished), this, &DVcsJob::slotProcessExited); connect(d->childproc, static_cast(&KProcess::error), this, &DVcsJob::slotProcessError); connect(d->childproc, &KProcess::readyReadStandardOutput, this, &DVcsJob::slotReceivedStdout); } DVcsJob::~DVcsJob() { delete d; } QDir DVcsJob::directory() const { return QDir(d->childproc->workingDirectory()); } DVcsJob& DVcsJob::operator<<(const QString& arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const char* arg) { *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const QStringList& args) { *d->childproc << args; return *this; } QStringList DVcsJob::dvcsCommand() const { return d->childproc->program(); } QString DVcsJob::output() const { QByteArray stdoutbuf = rawOutput(); int endpos = stdoutbuf.size(); if (d->status==JobRunning) { // We may have received only part of a code-point. apol: ASSERT? endpos = stdoutbuf.lastIndexOf('\n')+1; // Include the final newline or become 0, when there is no newline } return QString::fromLocal8Bit(stdoutbuf, endpos); } QByteArray DVcsJob::rawOutput() const { return d->output; } QByteArray DVcsJob::errorOutput() const { return d->errorOutput; } void DVcsJob::setIgnoreError(bool ignore) { d->ignoreError = ignore; } void DVcsJob::setResults(const QVariant &res) { d->results = res; } QVariant DVcsJob::fetchResults() { return d->results; } void DVcsJob::start() { Q_ASSERT_X(d->status != JobRunning, "DVCSjob::start", "Another proccess was started using this job class"); const QDir& workingdir = directory(); if( !workingdir.exists() ) { QString error = i18n( "Working Directory does not exist: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } if( !workingdir.isAbsolute() ) { QString error = i18n( "Working Directory is not absolute: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } QString commandDisplay = KShell::joinArgs(dvcsCommand()); qCDebug(VCS) << "Execute dvcs command:" << commandDisplay; QString service; if(d->vcsplugin) service = d->vcsplugin->objectName(); setObjectName(service+": "+commandDisplay); d->status = JobRunning; d->childproc->setOutputChannelMode(KProcess::SeparateChannels); //the started() and error() signals may be delayed! It causes crash with deferred deletion!!! d->childproc->start(); d->model->appendLine(directory().path() + "> " + commandDisplay); } void DVcsJob::setCommunicationMode(KProcess::OutputChannelMode comm) { d->childproc->setOutputChannelMode(comm); } void DVcsJob::cancel() { d->childproc->kill(); } void DVcsJob::slotProcessError( QProcess::ProcessError err ) { d->status = JobFailed; setError(OutputJob::FailedShownError); //we don't want to trigger a message box d->errorOutput = d->childproc->readAllStandardError(); QString displayCommand = KShell::joinArgs(dvcsCommand()); QString completeErrorText = i18n("Process '%1' exited with status %2\n%3", displayCommand, d->childproc->exitCode(), QString::fromLocal8Bit(d->errorOutput) ); setErrorText( completeErrorText ); QString errorValue; //if trolls add Q_ENUMS for QProcess, then we can use better solution than switch: //QMetaObject::indexOfEnumerator(char*), QQStringLiteral(QMetaEnum::valueToKey())... switch (err) { case QProcess::FailedToStart: errorValue = QStringLiteral("FailedToStart"); break; case QProcess::Crashed: errorValue = QStringLiteral("Crashed"); break; case QProcess::Timedout: errorValue = QStringLiteral("Timedout"); break; case QProcess::WriteError: errorValue = QStringLiteral("WriteError"); break; case QProcess::ReadError: errorValue = QStringLiteral("ReadError"); break; case QProcess::UnknownError: errorValue = QStringLiteral("UnknownError"); break; } qCDebug(VCS) << "Found an error while running" << displayCommand << ":" << errorValue << "Exit code is:" << d->childproc->exitCode(); qCDebug(VCS) << "Error:" << completeErrorText; displayOutput(QString::fromLocal8Bit(d->errorOutput)); d->model->appendLine(i18n("Command finished with error %1.", errorValue)); if(verbosity()==Silent) { setVerbosity(Verbose); startOutput(); } emitResult(); } void DVcsJob::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { d->status = JobSucceeded; d->model->appendLine(i18n("Command exited with value %1.", exitCode)); if (exitStatus == QProcess::CrashExit) slotProcessError(QProcess::Crashed); else if (exitCode != 0 && !d->ignoreError) slotProcessError(QProcess::UnknownError); else jobIsReady(); } void DVcsJob::displayOutput(const QString& data) { d->model->appendLines(data.split('\n')); } void DVcsJob::slotReceivedStdout() { QByteArray output = d->childproc->readAllStandardOutput(); // accumulate output d->output.append(output); displayOutput(QString::fromLocal8Bit(output)); } VcsJob::JobStatus DVcsJob::status() const { return d->status; } IPlugin* DVcsJob::vcsPlugin() const { return d->vcsplugin; } DVcsJob& DVcsJob::operator<<(const QUrl& url) { *d->childproc << url.toLocalFile(); return *this; } DVcsJob& DVcsJob::operator<<(const QList< QUrl >& urls) { foreach(const QUrl &url, urls) operator<<(url); return *this; } bool DVcsJob::doKill() { if (d->childproc->state() == QProcess::NotRunning) { return true; } static const int terminateKillTimeout = 1000; // ms d->childproc->terminate(); bool terminated = d->childproc->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->childproc->kill(); terminated = d->childproc->waitForFinished( terminateKillTimeout ); } return terminated; } void DVcsJob::jobIsReady() { emit readyForParsing(this); //let parsers to set status emitResult(); //KJob emit resultsReady(this); //VcsJob } KProcess* DVcsJob::process() {return d->childproc;}