File indexing completed on 2024-04-28 04:38:40

0001 /*
0002     SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "scriptappjob.h"
0009 #include "executescriptplugin.h"
0010 
0011 #include <QFileInfo>
0012 
0013 #include <KLocalizedString>
0014 #include <KProcess>
0015 #include <KSharedConfig>
0016 #include <KShell>
0017 
0018 #include <interfaces/ilaunchconfiguration.h>
0019 #include <interfaces/iruntimecontroller.h>
0020 #include <interfaces/iruntime.h>
0021 #include <outputview/outputmodel.h>
0022 #include <outputview/outputdelegate.h>
0023 #include <util/processlinemaker.h>
0024 #include <util/environmentprofilelist.h>
0025 
0026 #include <interfaces/icore.h>
0027 #include <interfaces/iplugincontroller.h>
0028 #include <interfaces/idocumentcontroller.h>
0029 #include <project/projectmodel.h>
0030 #include <util/path.h>
0031 
0032 #include "iexecutescriptplugin.h"
0033 #include "debug.h"
0034 
0035 using namespace KDevelop;
0036 
0037 ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg)
0038     : KDevelop::OutputJob( parent ), proc(new KProcess( this )), lineMaker(new KDevelop::ProcessLineMaker( proc, this ))
0039 {
0040     qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job";
0041     setCapabilities(Killable);
0042 
0043     auto* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecuteScriptPlugin"))->extension<IExecuteScriptPlugin>();
0044     Q_ASSERT(iface);
0045 
0046     const KDevelop::EnvironmentProfileList environmentProfiles(KSharedConfig::openConfig());
0047     QString envProfileName = iface->environmentProfileName(cfg);
0048 
0049     QString err;
0050     QString interpreterString = iface->interpreter( cfg, err );
0051     // check for errors happens in the executescript plugin already
0052     KShell::Errors err_;
0053     QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ );
0054     if ( interpreter.isEmpty() ) {
0055         // This should not happen, because of the checks done in the executescript plugin
0056         qCWarning(PLUGIN_EXECUTESCRIPT) << "no interpreter specified";
0057         return;
0058     }
0059 
0060     if( !err.isEmpty() )
0061     {
0062         setError( -1 );
0063         setErrorText( err );
0064         return;
0065     }
0066 
0067     QUrl script;
0068     if( !iface->runCurrentFile( cfg ) )
0069     {
0070         script = iface->script( cfg, err );
0071     } else {
0072         KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument();
0073         if( !document )
0074         {
0075             setError( -1 );
0076             setErrorText( i18n( "There is no active document to launch." ) );
0077             return;
0078         }
0079         script = ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(document->url())).toUrl();
0080     }
0081 
0082     if( !err.isEmpty() )
0083     {
0084         setError( -3 );
0085         setErrorText( err );
0086         return;
0087     }
0088 
0089     QString remoteHost = iface->remoteHost( cfg, err );
0090     if( !err.isEmpty() )
0091     {
0092         setError( -4 );
0093         setErrorText( err );
0094         return;
0095     }
0096 
0097     if (envProfileName.isEmpty()) {
0098         qCWarning(PLUGIN_EXECUTESCRIPT) << "Launch Configuration:" << cfg->name() << i18n("No environment profile specified, looks like a broken "
0099                        "configuration, please check run configuration '%1'. "
0100                        "Using default environment profile.", cfg->name() );
0101         envProfileName = environmentProfiles.defaultProfileName();
0102     }
0103 
0104     QStringList arguments = iface->arguments( cfg, err );
0105     if( !err.isEmpty() )
0106     {
0107         setError( -2 );
0108         setErrorText( err );
0109     }
0110 
0111     if( error() != 0 )
0112     {
0113         qCWarning(PLUGIN_EXECUTESCRIPT) << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText();
0114         return;
0115     }
0116 
0117     auto currentFilterMode = static_cast<KDevelop::OutputModel::OutputFilterStrategy>( iface->outputFilterModeId( cfg ) );
0118 
0119     setStandardToolView(KDevelop::IOutputView::RunView);
0120     setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll);
0121     auto* m = new KDevelop::OutputModel;
0122     m->setFilteringStrategy(currentFilterMode);
0123     setModel( m );
0124     setDelegate( new KDevelop::OutputDelegate );
0125 
0126     connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines );
0127     connect(proc, &QProcess::errorOccurred, this, &ScriptAppJob::processError);
0128     connect( proc, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished), this, &ScriptAppJob::processFinished );
0129 
0130     // Now setup the process parameters
0131 
0132     proc->setEnvironment(environmentProfiles.createEnvironment(envProfileName, proc->systemEnvironment()));
0133     QUrl wc = iface->workingDirectory( cfg );
0134     if( !wc.isValid() || wc.isEmpty() )
0135     {
0136         wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() );
0137     }
0138     proc->setWorkingDirectory( ICore::self()->runtimeController()->currentRuntime()->pathInRuntime(KDevelop::Path(wc)).toLocalFile() );
0139     proc->setProperty( "executable", interpreter.first() );
0140 
0141     QStringList program;
0142     if (!remoteHost.isEmpty()) {
0143         program << QStringLiteral("ssh");
0144         QStringList parts = remoteHost.split(QLatin1Char(':'));
0145         program << parts.first();
0146         if (parts.length() > 1) {
0147             program << QLatin1String("-p ") + parts.at(1);
0148         }
0149     }
0150     program << interpreter;
0151     program << script.toLocalFile();
0152     program << arguments;
0153 
0154     qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program;
0155 
0156     proc->setOutputChannelMode(KProcess::MergedChannels);
0157 
0158     proc->setProgram( program );
0159 
0160     setTitle(cfg->name());
0161 }
0162 
0163 
0164 void ScriptAppJob::start()
0165 {
0166     qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc;
0167     if( proc )
0168     {
0169         startOutput();
0170         appendLine( i18n("Starting: %1", proc->program().join(QLatin1Char( ' ' ) ) ) );
0171         ICore::self()->runtimeController()->currentRuntime()->startProcess(proc);
0172     } else
0173     {
0174         qCWarning(PLUGIN_EXECUTESCRIPT) << "No process, something went wrong when creating the job";
0175         // No process means we've returned early on from the constructor, some bad error happened
0176         emitResult();
0177     }
0178 }
0179 
0180 bool ScriptAppJob::doKill()
0181 {
0182     if( proc ) {
0183         proc->kill();
0184         appendLine( i18n( "*** Killed Application ***" ) );
0185     }
0186     return true;
0187 }
0188 
0189 
0190 void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status )
0191 {
0192     lineMaker->flushBuffers();
0193 
0194     if (exitCode == 0 && status == QProcess::NormalExit) {
0195         appendLine( i18n("*** Exited normally ***") );
0196     } else if (status == QProcess::NormalExit) {
0197         appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) );
0198         setError(OutputJob::FailedShownError);
0199     } else if (error() == KJob::KilledJobError) {
0200         appendLine( i18n("*** Process aborted ***") );
0201         setError(KJob::KilledJobError);
0202     } else {
0203         appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) );
0204         setError(OutputJob::FailedShownError);
0205     }
0206     qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done";
0207     emitResult();
0208 }
0209 
0210 void ScriptAppJob::processError( QProcess::ProcessError error )
0211 {
0212     qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError();
0213     qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput();
0214     qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString();
0215     if( error == QProcess::FailedToStart )
0216     {
0217         setError( FailedShownError );
0218         QString errmsg =  i18n("*** Could not start program '%1'. Make sure that the "
0219                            "path is specified correctly ***", proc->program().join(QLatin1Char( ' ' ) ) );
0220         appendLine( errmsg );
0221         setErrorText( errmsg );
0222         emitResult();
0223     }
0224     qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error";
0225 }
0226 
0227 void ScriptAppJob::appendLine(const QString& l)
0228 {
0229     if (KDevelop::OutputModel* m = model()) {
0230         m->appendLine(l);
0231     }
0232 }
0233 
0234 KDevelop::OutputModel* ScriptAppJob::model()
0235 {
0236     return qobject_cast<KDevelop::OutputModel*>(OutputJob::model());
0237 }
0238 
0239 #include "moc_scriptappjob.cpp"