diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp index d869139ae8..bd41ced9e3 100644 --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -1,439 +1,435 @@ /* 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 #include #include #include #include +#include #include namespace KDevelop { OutputExecuteJob::OutputExecuteJob( QObject* parent, OutputJob::OutputJobVerbosity verbosity ): OutputJob( parent, verbosity ), m_process( new KProcess( this ) ), m_lineMaker( new ProcessLineMaker( this ) ), // do not assign process to the line maker as we'll feed it data ourselves m_status( JobNotStarted ), m_properties( CheckWorkingDirectory | DisplayStdout ), m_filteringStrategy( OutputModel::NoFilter ), m_arguments(), m_privilegedExecutionCommand(), m_workingDirectory(), m_environmentProfile(), m_environmentOverrides(), m_jobName(), m_outputStarted( false ), m_processStdout(), m_processStderr() { m_process->setOutputChannelMode( KProcess::SeparateChannels ); m_process->setTextModeEnabled( true ); connect( m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(childProcessExited(int,QProcess::ExitStatus)) ); connect( m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(childProcessError(QProcess::ProcessError)) ); connect( m_process, SIGNAL(readyReadStandardOutput()), SLOT(childProcessStdout()) ); connect( m_process, SIGNAL(readyReadStandardError()), SLOT(childProcessStderr()) ); } OutputExecuteJob::~OutputExecuteJob() { if( m_process->state() != QProcess::NotRunning ) { doKill(); } Q_ASSERT( m_process->state() == QProcess::NotRunning ); } OutputExecuteJob::JobStatus OutputExecuteJob::status() const { return m_status; } OutputModel* OutputExecuteJob::model() const { return dynamic_cast ( OutputJob::model() ); } QStringList OutputExecuteJob::commandLine() const { return m_arguments; } OutputExecuteJob& OutputExecuteJob::operator<<( const QString& argument ) { m_arguments << argument; + updateCommandLine(); + updateJobName(); return *this; } OutputExecuteJob& OutputExecuteJob::operator<<( const QStringList& arguments ) { m_arguments << arguments; + updateCommandLine(); + updateJobName(); return *this; } QStringList OutputExecuteJob::privilegedExecutionCommand() const { return m_privilegedExecutionCommand; } void OutputExecuteJob::setPrivilegedExecutionCommand( const QStringList& command ) { m_privilegedExecutionCommand = command; } void OutputExecuteJob::setJobName( const QString& name ) { m_jobName = name; updateJobName(); } KUrl OutputExecuteJob::workingDirectory() const { return m_workingDirectory; } void OutputExecuteJob::setWorkingDirectory( const KUrl& url ) { m_workingDirectory = url; } void OutputExecuteJob::start() { Q_ASSERT( m_status == JobNotStarted ); m_status = JobRunning; const bool isBuilder = m_properties.testFlag( IsBuilderHint ); const KUrl effectiveWorkingDirectory = workingDirectory(); if( effectiveWorkingDirectory.isEmpty() ) { if( 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 if( m_properties.testFlag( CheckWorkingDirectory ) ) { // a directory is given and we need to check it if( !effectiveWorkingDirectory.isValid() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Invalid build directory '%1'", effectiveWorkingDirectory.prettyUrl() ) ); } else { setErrorText( i18n( "Invalid working directory '%1'", effectiveWorkingDirectory.prettyUrl() ) ); } return emitResult(); } else if( !effectiveWorkingDirectory.isLocalFile() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a local path", effectiveWorkingDirectory.prettyUrl() ) ); } else { setErrorText( i18n( "Working directory '%1' is not a local path", effectiveWorkingDirectory.prettyUrl() ) ); } return emitResult(); } else if( !QFileInfo( effectiveWorkingDirectory.toLocalFile() ).isDir() ) { setError( InvalidWorkingDirectoryError ); if( isBuilder ) { setErrorText( i18n( "Build directory '%1' is not a directory", effectiveWorkingDirectory.prettyUrl() ) ); } else { setErrorText( i18n( "Working directory '%1' is not a directory", effectiveWorkingDirectory.prettyUrl() ) ); } return emitResult(); } setModel( new OutputModel( effectiveWorkingDirectory ) ); } Q_ASSERT( model() ); model()->setFilteringStrategy( m_filteringStrategy ); setDelegate( new OutputDelegate ); // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( m_properties.testFlag( PostProcessOutput ) ) { connect( m_lineMaker, SIGNAL( receivedStdoutLines( QStringList ) ), SLOT( postProcessStdout( QStringList ) ) ); connect( m_lineMaker, SIGNAL( receivedStderrLines( QStringList ) ), SLOT( postProcessStderr( QStringList ) ) ); } else { connect( m_lineMaker, SIGNAL( receivedStdoutLines( QStringList ) ), model(), SLOT(appendLines(QStringList)) ); connect( m_lineMaker, SIGNAL( receivedStderrLines( QStringList ) ), model(), SLOT(appendLines(QStringList)) ); } if( !m_properties.testFlag( NoSilentOutput ) || verbosity() != Silent ) { m_outputStarted = true; startOutput(); } - const QString joinedCommandLine = joinCommandLine(); QString headerLine; if( !effectiveWorkingDirectory.isEmpty() ) { - headerLine = effectiveWorkingDirectory.toLocalFile( KUrl::RemoveTrailingSlash ) + "> " + joinedCommandLine; + headerLine = effectiveWorkingDirectory.toLocalFile( KUrl::RemoveTrailingSlash ) + "> " + m_joinedCommandLine; } else { - headerLine = joinedCommandLine; + headerLine = m_joinedCommandLine; } model()->appendLine( headerLine ); if( !effectiveWorkingDirectory.isEmpty() ) { m_process->setWorkingDirectory( effectiveWorkingDirectory.toLocalFile() ); } m_process->setProcessEnvironment( effectiveEnvironment() ); m_process->setProgram( effectiveCommandLine() ); m_process->start(); } bool OutputExecuteJob::doKill() { if( m_status != JobRunning ) return true; m_status = JobCanceled; m_process->terminate(); bool terminated = m_process->waitForFinished( 3000 ); if( !terminated ) { m_process->kill(); terminated = m_process->waitForFinished(); } m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Aborted ***") ); if( !terminated ) { // 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( m_status != JobRunning ) return; m_status = JobFailed; QString errorValue; switch( processError ) { case QProcess::FailedToStart: errorValue = i18n("Process had failed to start"); break; case QProcess::Crashed: errorValue = i18n("Process had crashed"); 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 had timed out"); break; default: case QProcess::UnknownError: errorValue = i18n("Exit code %1", m_process->exitCode()); break; } // Show the toolview if it's hidden for the user to be able to diagnose errors. if( !m_outputStarted ) { m_outputStarted = true; startOutput(); } setError( FailedShownError ); setErrorText( errorValue ); m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Failure: %1 ***", errorValue) ); emitResult(); } void OutputExecuteJob::childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ) { if( m_status != JobRunning ) return; if( exitStatus == QProcess::CrashExit ) { childProcessError( QProcess::Crashed ); } else if ( exitCode != 0 ) { childProcessError( QProcess::UnknownError ); } else { m_status = JobSucceeded; m_lineMaker->flushBuffers(); model()->appendLine( i18n("*** Finished ***") ); emitResult(); } } void OutputExecuteJob::childProcessStdout() { QByteArray out = m_process->readAllStandardOutput(); if( m_properties.testFlag( AccumulateStdout ) ) { m_processStdout += out; } if( m_properties.testFlag( DisplayStdout ) ) { m_lineMaker->slotReceivedStdout( out ); } } void OutputExecuteJob::childProcessStderr() { QByteArray err = m_process->readAllStandardError(); if( m_properties.testFlag( AccumulateStderr ) ) { m_processStderr += err; } if( m_properties.testFlag( DisplayStderr ) ) { m_lineMaker->slotReceivedStderr( err ); } } void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::postProcessStderr( const QStringList& lines ) { model()->appendLines( lines ); } void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { m_filteringStrategy = strategy; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const { return m_properties; } void OutputExecuteJob::setProperties( OutputExecuteJob::JobProperties properties, bool override ) { if( override ) { m_properties = properties; } else { m_properties |= properties; } } void OutputExecuteJob::unsetProperties( OutputExecuteJob::JobProperties properties ) { m_properties &= ~properties; } QString OutputExecuteJob::environmentProfile() const { return m_environmentProfile; } void OutputExecuteJob::setEnvironmentProfile( const QString& profile ) { m_environmentProfile = profile; } void OutputExecuteJob::addEnvironmentOverride( const QString& name, const QString& value ) { m_environmentOverrides[name] = value; } void OutputExecuteJob::removeEnvironmentOverride( const QString& name ) { m_environmentOverrides.remove( name ); } void OutputExecuteJob::mergeEnvironment( QProcessEnvironment& dest, const QMap& src ) { for( QMap::const_iterator it = src.begin(); it != src.end(); ++it ) { dest.insert( it.key(), it.value() ); } } QProcessEnvironment OutputExecuteJob::effectiveEnvironment() const { QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); const EnvironmentGroupList environmentGroup( KGlobal::config() ); mergeEnvironment( environment, environmentGroup.variables( environmentProfile() ) ); mergeEnvironment( environment, m_environmentOverrides ); if( m_properties.testFlag( PortableMessages ) ) { environment.insert( "LC_MESSAGES", "C" ); } return environment; } -QString OutputExecuteJob::joinCommandLine() const +void OutputExecuteJob::updateCommandLine() { - QStringList commandLine = effectiveCommandLine(); - for( QStringList::iterator it = commandLine.begin(); it != commandLine.end(); ++it ) { - // If an argument contains spaces, enclose it with quote signs. - if( it->contains( QRegExp("[[:space:]]") ) ) { - it->prepend('"').append('"'); - } - } - return commandLine.join(" "); + m_joinedCommandLine = KShell::joinArgs( effectiveCommandLine() ); } QStringList OutputExecuteJob::effectiveCommandLine() const { // If we need to use a su-like helper, invoke it as // "helper -- our command line". QStringList privilegedCommand = privilegedExecutionCommand(); if( !privilegedCommand.isEmpty() ) { return QStringList() << privilegedExecutionCommand() << "--" << commandLine(); } else { return commandLine(); } } void OutputExecuteJob::updateJobName() { - const QString joinedCommandLine = joinCommandLine(); QString jobName; if( m_properties.testFlag( AppendProcessString ) ) { if( !m_jobName.isEmpty() ) { - jobName = m_jobName + ": " + joinedCommandLine; + jobName = m_jobName + ": " + m_joinedCommandLine; } else { - jobName = joinedCommandLine; + jobName = m_joinedCommandLine; } } else { jobName = m_jobName; } setObjectName( jobName ); setTitle( jobName ); } } // namespace KDevelop #include "outputexecutejob.moc" \ No newline at end of file diff --git a/outputview/outputexecutejob.h b/outputview/outputexecutejob.h index 1669bd5d21..01bd470437 100644 --- a/outputview/outputexecutejob.h +++ b/outputview/outputexecutejob.h @@ -1,268 +1,268 @@ /* This file is part of KDevelop C opyright 2012 Ivan Shapoval*ov 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 OUTPUTEXECUTEJOB_H #define OUTPUTEXECUTEJOB_H #include "outputjob.h" #include "outputmodel.h" #include class KUrl; class KProcess; namespace KDevelop { class ProcessLineMaker; class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputExecuteJob : public OutputJob { Q_OBJECT public: enum JobStatus { JobRunning = 0, /**< The job is running */ JobSucceeded = 1, /**< The job has succeeded */ JobCanceled = 2, /**< The job has been cancelled */ JobFailed = 3, /**< The job has failed */ JobNotStarted = 4 /**< The job hasn't been started so far */ }; enum { InvalidWorkingDirectoryError = OutputJob::UserDefinedError, UserDefinedError }; enum JobProperty { AppendProcessString, /**< Whether to append a process string to the user-specified job name */ NeedWorkingDirectory, /**< Whether to require a non-empty working directory to be provided */ CheckWorkingDirectory, /**< Whether to verify the working directory (exists, is a directory, etc.) */ PortableMessages, /**< Whether to set LC_MESSAGES=C in the process' environment */ DisplayStdout, /**< Whether to pass process' stdout to the output model */ AccumulateStdout, /**< Whether to save process' stdout to the internal byte-array buffer for further consumption */ DisplayStderr, /**< Whether to pass process' stderr to the output model */ AccumulateStderr, /**< Whether to save process' stderr to the internal byte-array buffer for further consumption */ NoSilentOutput, /**< Whether to call \ref startOutput() only if verbosity is \ref OutputJob::Verbose */ PostProcessOutput, /**< Whether to connect line maker's signals to \ref postProcessStdout() and \ref postProcessStderr() */ IsBuilderHint, /**< Whether to use builder-specific messages to talk to user (e. g. "build directory" instead of "working directory" */ }; Q_FLAGS(JobProperty) Q_DECLARE_FLAGS(JobProperties, JobProperty) OutputExecuteJob( QObject* parent = 0, OutputJobVerbosity verbosity = OutputJob::Verbose ); virtual ~OutputExecuteJob(); /** * Get the job's status (associated with the process). * * @returns The job's status. * @see JobStatus */ JobStatus status() const; /** * Get the job's output model. * * @returns The job's output model, downcasted to \ref OutputModel */ OutputModel* model() const; /** * Returns a working directory for the job's process. * * @returns URL which has been set through \ref setWorkingDirectory(); empty URL if unset. */ virtual KUrl workingDirectory() const; /** * Set a working directory for the job's process. * Effective if \ref workingDirectory() hasn't been overridden. * * @param directory a valid local directory URL, or an empty URL to unset. */ void setWorkingDirectory( const KUrl& directory ); /** * Get process' command line. * * @returns The command line for the process, with first element in list being the program path. */ virtual QStringList commandLine() const; /** * Append an element to the command line argument list for this process. * If no executable is set yet, it will be set instead. * Effective if \ref commandLine() hasn't been overridden. * * @param argument the argument to add */ OutputExecuteJob& operator<<( const QString& argument ); /** * Append a list of elements to the command line argument list for this process. * If no executable is set yet, it will be set from the first argument in given list. * Effective if \ref commandLine() hasn't been overridden. * * @param arguments the arguments to add */ OutputExecuteJob& operator<<( const QStringList& arguments ); /** * Get the privilege escalation command ("su", "sudo", etc.) used for the job's process. * * @returns The privilege escalation command name and arguments; empty list if not set. */ virtual QStringList privilegedExecutionCommand() const; /** * Set the privilege escalation command ("su", "sudo", etc.) which will be used for the job's process. * Effective if \ref privilegedExecutionCommand() hasn't been overridden. * * @param command The privilege escalation command's name and arguments; empty list to unset. * @see privilegedCommand */ void setPrivilegedExecutionCommand( const QStringList& command ); /** * A convenience function to set the job name. * * Calls \ref setTitle() and \ref setObjectName(). * - * @note If you need the command-line to be appended to the job name, - * make sure that it is already configured upon calling this function. - * * @param name The name to set; empty string to use default (process string). */ void setJobName( const QString& name ); /** * Set the filtering strategy for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** * Get the current properties of the job. * * @note Default-set properties are: \ref CheckWorkingDirectory | \ref DisplayStdout. */ virtual JobProperties properties() const; /** * Set properties of the job. * Effective if \ref properties() hasn't been overridden. * * @param properties Which flags to add to the job. * @param override Whether to assign instead of doing bitwise OR. * @see JobProperties, properties(), unsetProperties() */ void setProperties( JobProperties properties, bool override = false ); /** * Unset properties of the job. * * @param properties Which flags to remove from the job * @see JobProperties, properties(), setProperties() */ void unsetProperties( JobProperties properties ); /** * Add a variable to the job's process environment. * * The variables added with this method override ones from the system environment and * the global environment profile, but are overridden by "PortableMessages" property. * * @param name The name of a variable to add * @param value The value of a variable to add; empty string to unset. */ void addEnvironmentOverride( const QString& name, const QString& value ); /** * Remove a variable from the override set. * * @param name The name of a variable to remove. * @note This does not force a variable to empty value; this is to undo the overriding itself. */ void removeEnvironmentOverride( const QString& name ); /** * Get the global environment profile name for the job's process. * * @returns The environment profile name to use in the job's process; empty if unset. */ virtual QString environmentProfile() const; /** * Set the environment profile name for the job's process. * Effective if \ref environmentProfile() hasn't been overridden. * * @param profile The name of profile to set. */ void setEnvironmentProfile( const QString& profile ); virtual void start(); protected: virtual bool doKill(); protected slots: // Redefine these functions if you want to post-process the output somehow // before it hits the output model. // Default implementations for either function call "model()->appendLines( lines );". // Do the same if you need the output to be visible. virtual void postProcessStdout( const QStringList& lines ); virtual void postProcessStderr( const QStringList& lines ); private slots: void childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); void childProcessError( QProcess::ProcessError processError ); void childProcessStdout(); void childProcessStderr(); private: - QString joinCommandLine() const; + void updateCommandLine(); void updateJobName(); static void mergeEnvironment( QProcessEnvironment& dest, const QMap& src ); QProcessEnvironment effectiveEnvironment() const; QStringList effectiveCommandLine() const; KProcess* m_process; ProcessLineMaker* m_lineMaker; JobStatus m_status; JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; QStringList m_arguments; QStringList m_privilegedExecutionCommand; KUrl m_workingDirectory; QString m_environmentProfile; QMap m_environmentOverrides; QString m_jobName; bool m_outputStarted; + // Just a cache. + QString m_joinedCommandLine; + QByteArray m_processStdout; QByteArray m_processStderr; }; } // namespace KDevelop #endif // OUTPUTEXECUTEJOB_H \ No newline at end of file