Files
qt-creator/src/plugins/vcsbase/vcscommand.cpp
Eike Ziller 8db0d3b0ff Fix lupdate issues
- missing Q_DECLARE_TR_FUNCTIONS
- some full qualifications needed for confused lupdate
- avoid breaking some translations from former TaskListPlugin,
  VcsCommand and highlighter settings refactoring

Change-Id: Ia3b34095512a7bad6903f0aff6095313ee39e3e4
Reviewed-by: hjk <hjk@qt.io>
2022-10-14 13:26:45 +00:00

406 lines
13 KiB
C++

// Copyright (C) 2016 Brian McGillion and Hugues Delorme
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "vcscommand.h"
#include "vcsbaseplugin.h"
#include "vcsoutputwindow.h"
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <utils/environment.h>
#include <utils/globalfilechangeblocker.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QCoreApplication>
#include <QFuture>
#include <QFutureWatcher>
#include <QTextCodec>
#include <QThread>
using namespace Core;
using namespace Utils;
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);
m_futureInterface.setProgressRange(0, 1);
}
Environment environment()
{
if (!(m_flags & RunFlags::ForceCLocale))
return m_environment;
m_environment.set("LANG", "C");
m_environment.set("LANGUAGE", "C");
return m_environment;
}
QString displayName() const;
int timeoutS() const;
void setup();
void cleanup();
void setupProcess(QtcProcess *process, const Job &job);
void installStdCallbacks(QtcProcess *process);
EventLoopMode eventLoopMode() const;
void handleDone(QtcProcess *process);
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 = {};
QFutureWatcher<void> m_watcher;
QList<Job> m_jobs;
int m_currentJob = 0;
std::unique_ptr<QtcProcess> m_process;
QString m_stdOut;
QString m_stdErr;
ProcessResult m_result = ProcessResult::StartFailed;
QFutureInterface<void> m_futureInterface;
RunFlags m_flags = RunFlags::None;
};
QString VcsCommandPrivate::displayName() const
{
if (!m_displayName.isEmpty())
return m_displayName;
if (m_jobs.isEmpty())
return VcsCommand::tr("UNKNOWN");
const Job &job = m_jobs.at(0);
QString result = job.command.executable().baseName();
if (!result.isEmpty())
result[0] = result.at(0).toTitleCase();
else
result = VcsCommand::tr("UNKNOWN");
if (!job.command.arguments().isEmpty())
result += ' ' + job.command.splitArguments().at(0);
return result;
}
int VcsCommandPrivate::timeoutS() const
{
return std::accumulate(m_jobs.cbegin(), m_jobs.cend(), 0,
[](int sum, const Job &job) { return sum + job.timeoutS; });
}
void VcsCommandPrivate::setup()
{
m_futureInterface.reportStarted();
if (m_flags & RunFlags::ExpectRepoChanges) {
QMetaObject::invokeMethod(GlobalFileChangeBlocker::instance(), [] {
GlobalFileChangeBlocker::instance()->forceBlocked(true);
});
}
}
void VcsCommandPrivate::cleanup()
{
QTC_ASSERT(m_futureInterface.isRunning(), return);
m_futureInterface.reportFinished();
if (m_flags & RunFlags::ExpectRepoChanges) {
QMetaObject::invokeMethod(GlobalFileChangeBlocker::instance(), [] {
GlobalFileChangeBlocker::instance()->forceBlocked(false);
});
}
}
void VcsCommandPrivate::setupProcess(QtcProcess *process, const Job &job)
{
process->setExitCodeInterpreter(job.exitCodeInterpreter);
// TODO: Handle it properly in QtcProcess when QtcProcess::runBlocking() isn't used.
process->setTimeoutS(job.timeoutS);
if (!job.workingDirectory.isEmpty())
process->setWorkingDirectory(job.workingDirectory);
if (!(m_flags & RunFlags::SuppressCommandLogging))
emit q->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);
installStdCallbacks(process);
}
void VcsCommandPrivate::installStdCallbacks(QtcProcess *process)
{
if (!(m_flags & RunFlags::MergeOutputChannels) && (m_flags & RunFlags::ProgressiveOutput
|| !(m_flags & RunFlags::SuppressStdErr))) {
process->setStdErrCallback([this](const QString &text) {
if (m_progressParser)
m_progressParser(m_futureInterface, text);
if (!(m_flags & RunFlags::SuppressStdErr))
emit q->appendError(text);
if (m_flags & RunFlags::ProgressiveOutput)
emit q->stdErrText(text);
});
}
// connect stdout to the output window if desired
if (m_progressParser || m_flags & RunFlags::ProgressiveOutput
|| m_flags & RunFlags::ShowStdOut) {
process->setStdOutCallback([this](const QString &text) {
if (m_progressParser)
m_progressParser(m_futureInterface, text);
if (m_flags & RunFlags::ShowStdOut) {
if (m_flags & RunFlags::SilentOutput)
emit q->appendSilently(text);
else
emit q->append(text);
}
if (m_flags & RunFlags::ProgressiveOutput)
emit q->stdOutText(text);
});
}
// TODO: Implement it here
// m_process->setTimeOutMessageBoxEnabled(true);
}
EventLoopMode VcsCommandPrivate::eventLoopMode() const
{
if ((m_flags & RunFlags::UseEventLoop) && QThread::currentThread() == qApp->thread())
return EventLoopMode::On;
return EventLoopMode::Off;
}
void VcsCommandPrivate::handleDone(QtcProcess *process)
{
// Success/Fail message in appropriate window?
if (process->result() == ProcessResult::FinishedWithSuccess) {
if (m_flags & RunFlags::ShowSuccessMessage)
emit q->appendMessage(process->exitMessage());
} else if (!(m_flags & RunFlags::SuppressFailMessage)) {
emit q->appendError(process->exitMessage());
}
emit q->runCommandFinished(process->workingDirectory());
}
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 QtcProcess);
connect(m_process.get(), &QtcProcess::done, this, &VcsCommandPrivate::processDone);
setupProcess(m_process.get(), m_jobs.at(m_currentJob));
m_process->start();
}
void VcsCommandPrivate::processDone()
{
handleDone(m_process.get());
m_stdOut += m_process->cleanedStdOut();
m_stdErr += m_process->cleanedStdErr();
m_result = m_process->result();
++m_currentJob;
const bool success = m_process->result() == ProcessResult::FinishedWithSuccess;
if (m_currentJob < m_jobs.count() && success) {
m_process.release()->deleteLater();
startNextJob();
return;
}
emit q->done();
if (!success)
m_futureInterface.reportCanceled();
cleanup();
// As it is used asynchronously, we need to delete ourselves
q->deleteLater();
}
} // namespace Internal
VcsCommand::VcsCommand(const FilePath &workingDirectory, const Environment &environment) :
d(new Internal::VcsCommandPrivate(this, workingDirectory, environment))
{
connect(&d->m_watcher, &QFutureWatcher<void>::canceled, this, &VcsCommand::cancel);
VcsOutputWindow::setRepository(d->m_defaultWorkingDirectory);
VcsOutputWindow *outputWindow = VcsOutputWindow::instance(); // Keep me here, just to be sure it's not instantiated in other thread
connect(this, &VcsCommand::append, outputWindow, [outputWindow](const QString &t) {
outputWindow->append(t);
});
connect(this, &VcsCommand::appendSilently, outputWindow, &VcsOutputWindow::appendSilently);
connect(this, &VcsCommand::appendError, outputWindow, &VcsOutputWindow::appendError);
connect(this, &VcsCommand::appendCommand, outputWindow, &VcsOutputWindow::appendCommand);
connect(this, &VcsCommand::appendMessage, outputWindow, &VcsOutputWindow::appendMessage);
const auto connection = connect(this, &VcsCommand::runCommandFinished,
this, &VcsCommand::postRunCommand);
connect(ICore::instance(), &ICore::coreAboutToClose, this, [this, connection] {
disconnect(connection);
d->m_process.reset();
if (d->m_futureInterface.isRunning()) {
d->m_futureInterface.reportCanceled();
d->cleanup();
}
});
}
void VcsCommand::postRunCommand(const FilePath &workingDirectory)
{
if (!(d->m_flags & RunFlags::ExpectRepoChanges))
return;
// TODO tell the document manager that the directory now received all expected changes
// Core::DocumentManager::unexpectDirectoryChange(d->m_workingDirectory);
VcsManager::emitRepositoryChanged(workingDirectory);
}
VcsCommand::~VcsCommand()
{
if (d->m_futureInterface.isRunning()) {
d->m_futureInterface.reportCanceled();
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();
d->m_watcher.setFuture(d->m_futureInterface.future());
if ((d->m_flags & RunFlags::SuppressCommandLogging))
return;
const QString name = d->displayName();
const auto id = Id::fromString(name + QLatin1String(".action"));
if (d->m_progressParser)
ProgressManager::addTask(d->m_futureInterface.future(), name, id);
else
ProgressManager::addTimedTask(d->m_futureInterface, name, id, qMax(2, d->timeoutS() / 5));
}
void VcsCommand::cancel()
{
d->m_futureInterface.reportCanceled();
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 Utils::FilePath &workingDirectory,
const Utils::Environment &environment,
const Utils::CommandLine &command, RunFlags flags,
int timeoutS, QTextCodec *codec)
{
VcsCommand vcsCommand(workingDirectory, environment);
vcsCommand.addFlags(flags);
vcsCommand.setCodec(codec);
return vcsCommand.runBlockingHelper(command, timeoutS);
}
CommandResult VcsCommand::runBlockingHelper(const CommandLine &command, int timeoutS)
{
QtcProcess process;
if (command.executable().isEmpty())
return {};
d->setupProcess(&process, {command, timeoutS, d->m_defaultWorkingDirectory, {}});
const EventLoopMode eventLoopMode = d->eventLoopMode();
process.setTimeOutMessageBoxEnabled(eventLoopMode == EventLoopMode::On);
process.runBlocking(eventLoopMode);
d->handleDone(&process);
return CommandResult(process);
}
void VcsCommand::setCodec(QTextCodec *codec)
{
d->m_codec = codec;
}
//! Use \a parser to parse progress data from stdout. Command takes ownership of \a parser
void VcsCommand::setProgressParser(const ProgressParser &parser)
{
d->m_progressParser = parser;
}
CommandResult::CommandResult(const QtcProcess &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())
{}
} // namespace VcsBase