Files
qt-creator/src/plugins/vcsbase/vcscommand.cpp

344 lines
10 KiB
C++
Raw Normal View History

// Copyright (C) 2016 Brian McGillion and Hugues Delorme
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "vcscommand.h"
#include "vcsbaseplugin.h"
#include "vcsoutputwindow.h"
#include <coreplugin/icore.h>
#include <utils/environment.h>
#include <utils/globalfilechangeblocker.h>
#include <utils/process.h>
#include <utils/qtcassert.h>
#include <utils/threadutils.h>
#include <QTextCodec>
using namespace Core;
using namespace Utils;
using namespace std::chrono;
namespace VcsBase {
namespace Internal {
class VcsCommandPrivate : public QObject
{
public:
struct Job {
CommandLine command;
int timeoutS = 10;
FilePath workingDirectory;
ExitCodeInterpreter exitCodeInterpreter = {};
};
VcsCommandPrivate(VcsCommand *vcsCommand, const FilePath &defaultWorkingDirectory,
const Environment &environment)
: q(vcsCommand)
, m_defaultWorkingDirectory(defaultWorkingDirectory)
, m_environment(environment)
{
VcsBase::setProcessEnvironment(&m_environment);
}
Environment environment()
{
if (!(m_flags & RunFlags::ForceCLocale))
return m_environment;
m_environment.set("LANG", "C");
m_environment.set("LANGUAGE", "C");
return m_environment;
}
void setup();
void cleanup();
void setupProcess(Process *process, const Job &job);
void installStdCallbacks(Process *process);
EventLoopMode eventLoopMode() const;
ProcessResult handleDone(Process *process, const Job &job) const;
void startAll();
void startNextJob();
void processDone();
VcsCommand *q = nullptr;
QString m_displayName;
const FilePath m_defaultWorkingDirectory;
Environment m_environment;
QTextCodec *m_codec = nullptr;
ProgressParser m_progressParser = {};
QList<Job> m_jobs;
int m_currentJob = 0;
std::unique_ptr<Process> m_process;
QString m_stdOut;
QString m_stdErr;
ProcessResult m_result = ProcessResult::StartFailed;
RunFlags m_flags = RunFlags::None;
};
void VcsCommandPrivate::setup()
{
VcsOutputWindow::setRepository(m_defaultWorkingDirectory);
if (m_flags & RunFlags::ExpectRepoChanges)
GlobalFileChangeBlocker::instance()->forceBlocked(true);
}
void VcsCommandPrivate::cleanup()
{
VcsOutputWindow::clearRepository();
if (m_flags & RunFlags::ExpectRepoChanges)
GlobalFileChangeBlocker::instance()->forceBlocked(false);
}
void VcsCommandPrivate::setupProcess(Process *process, const Job &job)
{
if (!job.workingDirectory.isEmpty())
process->setWorkingDirectory(job.workingDirectory);
if (!(m_flags & RunFlags::SuppressCommandLogging))
VcsOutputWindow::appendCommand(job.workingDirectory, job.command);
process->setCommand(job.command);
process->setDisableUnixTerminal();
process->setEnvironment(environment());
if (m_flags & RunFlags::MergeOutputChannels)
process->setProcessChannelMode(QProcess::MergedChannels);
if (m_codec)
process->setCodec(m_codec);
process->setUseCtrlCStub(true);
installStdCallbacks(process);
if (m_flags & RunFlags::SuppressCommandLogging)
return;
ProcessProgress *progress = new ProcessProgress(process);
progress->setDisplayName(m_displayName);
progress->setExpectedDuration(seconds(qMin(1, job.timeoutS / 5)));
if (m_progressParser)
progress->setProgressParser(m_progressParser);
}
void VcsCommandPrivate::installStdCallbacks(Process *process)
{
if (!(m_flags & RunFlags::MergeOutputChannels) && (m_flags & RunFlags::ProgressiveOutput
|| m_progressParser || !(m_flags & RunFlags::SuppressStdErr))) {
process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine);
connect(process, &Process::textOnStandardError, this, [this](const QString &text) {
if (!(m_flags & RunFlags::SuppressStdErr))
VcsOutputWindow::appendError(text);
if (m_flags & RunFlags::ProgressiveOutput)
emit q->stdErrText(text);
});
}
if (m_progressParser || m_flags & RunFlags::ProgressiveOutput
|| m_flags & RunFlags::ShowStdOut) {
process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
connect(process, &Process::textOnStandardOutput, this, [this](const QString &text) {
if (m_flags & RunFlags::ShowStdOut)
VcsOutputWindow::append(text);
if (m_flags & RunFlags::ProgressiveOutput)
emit q->stdOutText(text);
});
}
}
EventLoopMode VcsCommandPrivate::eventLoopMode() const
{
if ((m_flags & RunFlags::UseEventLoop) && isMainThread())
return EventLoopMode::On;
return EventLoopMode::Off;
}
ProcessResult VcsCommandPrivate::handleDone(Process *process, const Job &job) const
{
ProcessResult result;
if (job.exitCodeInterpreter && process->error() != QProcess::FailedToStart
&& process->exitStatus() == QProcess::NormalExit) {
result = job.exitCodeInterpreter(process->exitCode());
} else {
result = process->result();
}
const QString message = Process::exitMessage(process->commandLine(), result,
process->exitCode(), process->processDuration());
// Success/Fail message in appropriate window?
if (result == ProcessResult::FinishedWithSuccess) {
if (m_flags & RunFlags::ShowSuccessMessage)
VcsOutputWindow::appendMessage(message);
} else if (!(m_flags & RunFlags::SuppressFailMessage)) {
VcsOutputWindow::appendError(message);
}
if (m_flags & RunFlags::ExpectRepoChanges) {
// TODO tell the document manager that the directory now received all expected changes
// DocumentManager::unexpectDirectoryChange(d->m_workingDirectory);
VcsManager::emitRepositoryChanged(process->workingDirectory());
}
return result;
}
void VcsCommandPrivate::startAll()
{
// Check that the binary path is not empty
QTC_ASSERT(!m_jobs.isEmpty(), return);
QTC_ASSERT(!m_process, return);
setup();
m_currentJob = 0;
startNextJob();
}
void VcsCommandPrivate::startNextJob()
{
QTC_ASSERT(m_currentJob < m_jobs.count(), return);
m_process.reset(new Process);
connect(m_process.get(), &Process::done, this, &VcsCommandPrivate::processDone);
setupProcess(m_process.get(), m_jobs.at(m_currentJob));
m_process->start();
}
void VcsCommandPrivate::processDone()
{
m_result = handleDone(m_process.get(), m_jobs.at(m_currentJob));
m_stdOut += m_process->cleanedStdOut();
m_stdErr += m_process->cleanedStdErr();
++m_currentJob;
if (m_currentJob < m_jobs.count() && m_result == ProcessResult::FinishedWithSuccess) {
m_process.release()->deleteLater();
startNextJob();
return;
}
emit q->done();
cleanup();
q->deleteLater();
}
} // namespace Internal
VcsCommand::VcsCommand(const FilePath &workingDirectory, const Environment &environment) :
d(new Internal::VcsCommandPrivate(this, workingDirectory, environment))
{
connect(ICore::instance(), &ICore::coreAboutToClose, this, [this] {
if (d->m_process && d->m_process->isRunning())
d->cleanup();
d->m_process.reset();
});
}
VcsCommand::~VcsCommand()
{
if (d->m_process && d->m_process->isRunning())
d->cleanup();
delete d;
}
void VcsCommand::setDisplayName(const QString &name)
{
d->m_displayName = name;
}
void VcsCommand::addFlags(RunFlags f)
{
d->m_flags |= f;
}
void VcsCommand::addJob(const CommandLine &command, int timeoutS,
const FilePath &workingDirectory,
const ExitCodeInterpreter &interpreter)
{
QTC_ASSERT(!command.executable().isEmpty(), return);
d->m_jobs.push_back({command, timeoutS, !workingDirectory.isEmpty()
? workingDirectory : d->m_defaultWorkingDirectory, interpreter});
}
void VcsCommand::start()
{
if (d->m_jobs.empty())
return;
d->startAll();
}
void VcsCommand::cancel()
{
if (d->m_process) {
// TODO: we may want to call cancel here...
d->m_process->stop();
// TODO: we may want to not wait here...
d->m_process->waitForFinished();
d->m_process.reset();
}
}
QString VcsCommand::cleanedStdOut() const
{
return d->m_stdOut;
}
QString VcsCommand::cleanedStdErr() const
{
return d->m_stdErr;
}
ProcessResult VcsCommand::result() const
{
return d->m_result;
}
CommandResult VcsCommand::runBlocking(const FilePath &workingDirectory,
const Environment &environment,
const CommandLine &command, RunFlags flags,
int timeoutS, QTextCodec *codec)
{
VcsCommand vcsCommand(workingDirectory, environment);
vcsCommand.addFlags(flags);
vcsCommand.setCodec(codec);
return vcsCommand.runBlockingHelper(command, timeoutS);
}
// TODO: change timeout to std::chrono::seconds
CommandResult VcsCommand::runBlockingHelper(const CommandLine &command, int timeoutS)
{
Process process;
if (command.executable().isEmpty())
return {};
const Internal::VcsCommandPrivate::Job job{command, timeoutS, d->m_defaultWorkingDirectory};
d->setupProcess(&process, job);
const EventLoopMode eventLoopMode = d->eventLoopMode();
process.setTimeOutMessageBoxEnabled(eventLoopMode == EventLoopMode::On);
process.runBlocking(seconds(timeoutS), eventLoopMode);
d->handleDone(&process, job);
return CommandResult(process);
}
void VcsCommand::setCodec(QTextCodec *codec)
{
d->m_codec = codec;
}
void VcsCommand::setProgressParser(const ProgressParser &parser)
{
d->m_progressParser = parser;
}
CommandResult::CommandResult(const Process &process)
: m_result(process.result())
, m_exitCode(process.exitCode())
, m_exitMessage(process.exitMessage())
, m_cleanedStdOut(process.cleanedStdOut())
, m_cleanedStdErr(process.cleanedStdErr())
, m_rawStdOut(process.rawStdOut())
{}
CommandResult::CommandResult(const VcsCommand &command)
: m_result(command.result())
, m_cleanedStdOut(command.cleanedStdOut())
, m_cleanedStdErr(command.cleanedStdErr())
{}
} // namespace VcsBase