diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp index 19dfb74f630..83ca7470191 100644 --- a/src/plugins/remotelinux/linuxdevicetester.cpp +++ b/src/plugins/remotelinux/linuxdevicetester.cpp @@ -10,42 +10,34 @@ #include #include -#include #include #include #include -#include using namespace ProjectExplorer; using namespace Utils; +using namespace Utils::Tasking; namespace RemoteLinux { namespace Internal { -namespace { - -enum State { Inactive, - TestingEcho, - TestingUname, - TestingPorts, - TestingSftp, - TestingRsync, - TestingCommands }; - -} // anonymous namespace class GenericLinuxDeviceTesterPrivate { public: - IDevice::Ptr device; - QtcProcess echoProcess; - QtcProcess unameProcess; - DeviceUsedPortsGatherer portsGatherer; - FileTransfer fileTransfer; - State state = Inactive; - bool sftpWorks = false; - int currentCommandIndex = 0; - bool commandFailed = false; - QtcProcess commandsProcess; + GenericLinuxDeviceTesterPrivate(GenericLinuxDeviceTester *tester) : q(tester) {} + + TaskItem echoTask() const; + TaskItem unameTask() const; + TaskItem gathererTask() const; + TaskItem transferTask(FileTransferMethod method); + TaskItem transferTasks(); + TaskItem commandTask(const QString &commandName) const; + TaskItem commandTasks() const; + + GenericLinuxDeviceTester *q = nullptr; + IDevice::Ptr m_device; + std::unique_ptr m_taskTree; + bool m_sftpWorks = false; }; const QStringList s_commandsToTest = {"base64", @@ -80,267 +72,203 @@ const QStringList s_commandsToTest = {"base64", // other possible commands (checked for qnx): // "awk", "grep", "netstat", "print", "pidin", "sleep", "uname" +static const char s_echoContents[] = "Hello Remote World!"; + +TaskItem GenericLinuxDeviceTesterPrivate::echoTask() const +{ + const auto setup = [this](QtcProcess &process) { + emit q->progressMessage(Tr::tr("Sending echo to device...")); + process.setCommand({m_device->filePath("echo"), {s_echoContents}}); + }; + const auto done = [this](const QtcProcess &process) { + const QString reply = process.cleanedStdOut().chopped(1); // Remove trailing '\n' + if (reply != s_echoContents) + emit q->errorMessage(Tr::tr("Device replied to echo with unexpected contents.") + '\n'); + else + emit q->progressMessage(Tr::tr("Device replied to echo with expected contents.") + '\n'); + }; + const auto error = [this](const QtcProcess &process) { + const QString stdErrOutput = process.cleanedStdErr(); + if (!stdErrOutput.isEmpty()) + emit q->errorMessage(Tr::tr("echo failed: %1").arg(stdErrOutput) + '\n'); + else + emit q->errorMessage(Tr::tr("echo failed.") + '\n'); + }; + return Process(setup, done, error); +} + +TaskItem GenericLinuxDeviceTesterPrivate::unameTask() const +{ + const auto setup = [this](QtcProcess &process) { + emit q->progressMessage(Tr::tr("Checking kernel version...")); + process.setCommand({m_device->filePath("uname"), {"-rsm"}}); + }; + const auto done = [this](const QtcProcess &process) { + emit q->progressMessage(process.cleanedStdOut()); + }; + const auto error = [this](const QtcProcess &process) { + const QString stdErrOutput = process.cleanedStdErr(); + if (!stdErrOutput.isEmpty()) + emit q->errorMessage(Tr::tr("uname failed: %1").arg(stdErrOutput) + '\n'); + else + emit q->errorMessage(Tr::tr("uname failed.") + '\n'); + }; + return Tasking::Group { + optional, + Process(setup, done, error) + }; +} + +TaskItem GenericLinuxDeviceTesterPrivate::gathererTask() const +{ + const auto setup = [this](DeviceUsedPortsGatherer &gatherer) { + emit q->progressMessage(Tr::tr("Checking if specified ports are available...")); + gatherer.setDevice(m_device); + }; + const auto done = [this](const DeviceUsedPortsGatherer &gatherer) { + if (gatherer.usedPorts().isEmpty()) { + emit q->progressMessage(Tr::tr("All specified ports are available.") + '\n'); + } else { + const QString portList = transform(gatherer.usedPorts(), [](const Port &port) { + return QString::number(port.number()); + }).join(", "); + emit q->errorMessage(Tr::tr("The following specified ports are currently in use: %1") + .arg(portList) + '\n'); + } + }; + const auto error = [this](const DeviceUsedPortsGatherer &gatherer) { + emit q->errorMessage(Tr::tr("Error gathering ports: %1").arg(gatherer.errorString()) + '\n'); + }; + return PortGatherer(setup, done, error); +} + +TaskItem GenericLinuxDeviceTesterPrivate::transferTask(FileTransferMethod method) +{ + const auto setup = [this, method](FileTransfer &transfer) { + emit q->progressMessage(Tr::tr("Checking whether \"%1\" works...") + .arg(FileTransfer::transferMethodName(method))); + transfer.setTransferMethod(method); + transfer.setTestDevice(m_device); + }; + const auto done = [this, method](const FileTransfer &) { + const QString methodName = FileTransfer::transferMethodName(method); + emit q->progressMessage(Tr::tr("\"%1\" is functional.\n").arg(methodName)); + if (method == FileTransferMethod::Rsync) + m_device->setExtraData(Constants::SupportsRSync, true); + else + m_sftpWorks = true; + }; + const auto error = [this, method](const FileTransfer &transfer) { + const QString methodName = FileTransfer::transferMethodName(method); + const ProcessResultData resultData = transfer.resultData(); + QString error; + if (resultData.m_error == QProcess::FailedToStart) { + error = Tr::tr("Failed to start \"%1\": %2\n").arg(methodName, resultData.m_errorString); + } else if (resultData.m_exitStatus == QProcess::CrashExit) { + error = Tr::tr("\"%1\" crashed.\n").arg(methodName); + } else if (resultData.m_exitCode != 0) { + error = Tr::tr("\"%1\" failed with exit code %2: %3\n") + .arg(methodName).arg(resultData.m_exitCode).arg(resultData.m_errorString); + } + emit q->errorMessage(error); + if (method == FileTransferMethod::Rsync) { + m_device->setExtraData(Constants::SupportsRSync, false); + if (!m_sftpWorks) + return; + const QString sftp = FileTransfer::transferMethodName(FileTransferMethod::Sftp); + const QString rsync = methodName; + emit q->progressMessage(Tr::tr("\"%1\" will be used for deployment, because \"%2\" " + "is not available.\n").arg(sftp, rsync)); + } + }; + return Transfer(setup, done, error); +} + +TaskItem GenericLinuxDeviceTesterPrivate::transferTasks() +{ + return Tasking::Group { + continueOnDone, + transferTask(FileTransferMethod::Sftp), + transferTask(FileTransferMethod::Rsync), + OnGroupError([this] { emit q->errorMessage(Tr::tr("Deployment to this device will not " + "work out of the box.\n")); + }) + }; +} + +TaskItem GenericLinuxDeviceTesterPrivate::commandTask(const QString &commandName) const +{ + const auto setup = [this, commandName](QtcProcess &process) { + emit q->progressMessage(Tr::tr("%1...").arg(commandName)); + CommandLine command{m_device->filePath("/bin/sh"), {"-c"}}; + command.addArgs(QLatin1String("\"command -v %1\"").arg(commandName), CommandLine::Raw); + process.setCommand(command); + }; + const auto done = [this, commandName](const QtcProcess &) { + emit q->progressMessage(Tr::tr("%1 found.").arg(commandName)); + }; + const auto error = [this, commandName](const QtcProcess &process) { + const QString message = process.result() == ProcessResult::StartFailed + ? Tr::tr("An error occurred while checking for %1.").arg(commandName) + + '\n' + process.errorString() + : Tr::tr("%1 not found.").arg(commandName); + emit q->errorMessage(message); + }; + return Process(setup, done, error); +} + +TaskItem GenericLinuxDeviceTesterPrivate::commandTasks() const +{ + QList tasks {continueOnError}; + tasks.append(OnGroupSetup([this] { + emit q->progressMessage(Tr::tr("Checking if required commands are available...")); + })); + for (const QString &commandName : s_commandsToTest) + tasks.append(commandTask(commandName)); + return Tasking::Group {tasks}; +} + } // namespace Internal using namespace Internal; GenericLinuxDeviceTester::GenericLinuxDeviceTester(QObject *parent) - : DeviceTester(parent), d(new GenericLinuxDeviceTesterPrivate) + : DeviceTester(parent), d(new GenericLinuxDeviceTesterPrivate(this)) { - connect(&d->echoProcess, &QtcProcess::done, this, - &GenericLinuxDeviceTester::handleEchoDone); - connect(&d->unameProcess, &QtcProcess::done, this, - &GenericLinuxDeviceTester::handleUnameDone); - connect(&d->portsGatherer, &DeviceUsedPortsGatherer::error, - this, &GenericLinuxDeviceTester::handlePortsGathererError); - connect(&d->portsGatherer, &DeviceUsedPortsGatherer::portListReady, - this, &GenericLinuxDeviceTester::handlePortsGathererDone); - connect(&d->fileTransfer, &FileTransfer::done, - this, &GenericLinuxDeviceTester::handleFileTransferDone); - connect(&d->commandsProcess, &QtcProcess::done, - this, &GenericLinuxDeviceTester::handleCommandDone); } GenericLinuxDeviceTester::~GenericLinuxDeviceTester() = default; void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguration) { - QTC_ASSERT(d->state == Inactive, return); + QTC_ASSERT(!d->m_taskTree, return); - d->device = deviceConfiguration; + d->m_sftpWorks = false; + d->m_device = deviceConfiguration; - testEcho(); + auto allFinished = [this](DeviceTester::TestResult testResult) { + emit finished(testResult); + d->m_taskTree.release()->deleteLater(); + }; + + const Tasking::Group root { + d->echoTask(), + d->unameTask(), + d->gathererTask(), + d->transferTasks(), + d->commandTasks(), + OnGroupDone(std::bind(allFinished, TestSuccess)), + OnGroupError(std::bind(allFinished, TestFailure)) + }; + d->m_taskTree.reset(new TaskTree(root)); + d->m_taskTree->start(); } void GenericLinuxDeviceTester::stopTest() { - QTC_ASSERT(d->state != Inactive, return); - - switch (d->state) { - case TestingEcho: - d->echoProcess.close(); - break; - case TestingPorts: - d->portsGatherer.stop(); - break; - case TestingUname: - d->unameProcess.close(); - break; - case TestingSftp: - case TestingRsync: - d->fileTransfer.stop(); - break; - case TestingCommands: - d->commandsProcess.close(); - break; - case Inactive: - break; - } - - setFinished(TestFailure); -} - -static const char s_echoContents[] = "Hello Remote World!"; - -void GenericLinuxDeviceTester::testEcho() -{ - d->state = TestingEcho; - emit progressMessage(Tr::tr("Sending echo to device...")); - - d->echoProcess.setCommand({d->device->filePath("echo"), {s_echoContents}}); - d->echoProcess.start(); -} - -void GenericLinuxDeviceTester::handleEchoDone() -{ - QTC_ASSERT(d->state == TestingEcho, return); - if (d->echoProcess.result() != ProcessResult::FinishedWithSuccess) { - const QByteArray stdErrOutput = d->echoProcess.readAllStandardError(); - if (!stdErrOutput.isEmpty()) - emit errorMessage(Tr::tr("echo failed: %1").arg(QString::fromUtf8(stdErrOutput)) + '\n'); - else - emit errorMessage(Tr::tr("echo failed.") + '\n'); - setFinished(TestFailure); - return; - } - - const QString reply = d->echoProcess.cleanedStdOut().chopped(1); // Remove trailing \n - if (reply != s_echoContents) - emit errorMessage(Tr::tr("Device replied to echo with unexpected contents.") + '\n'); - else - emit progressMessage(Tr::tr("Device replied to echo with expected contents.") + '\n'); - - testUname(); -} - -void GenericLinuxDeviceTester::testUname() -{ - d->state = TestingUname; - emit progressMessage(Tr::tr("Checking kernel version...")); - - d->unameProcess.setCommand({d->device->filePath("uname"), {"-rsm"}}); - d->unameProcess.start(); -} - -void GenericLinuxDeviceTester::handleUnameDone() -{ - QTC_ASSERT(d->state == TestingUname, return); - - if (!d->unameProcess.errorString().isEmpty() || d->unameProcess.exitCode() != 0) { - const QByteArray stderrOutput = d->unameProcess.readAllStandardError(); - if (!stderrOutput.isEmpty()) - emit errorMessage(Tr::tr("uname failed: %1").arg(QString::fromUtf8(stderrOutput)) + QLatin1Char('\n')); - else - emit errorMessage(Tr::tr("uname failed.") + QLatin1Char('\n')); - } else { - emit progressMessage(QString::fromUtf8(d->unameProcess.readAllStandardOutput())); - } - - testPortsGatherer(); -} - -void GenericLinuxDeviceTester::testPortsGatherer() -{ - d->state = TestingPorts; - emit progressMessage(Tr::tr("Checking if specified ports are available...")); - - d->portsGatherer.setDevice(d->device); - d->portsGatherer.start(); -} - -void GenericLinuxDeviceTester::handlePortsGathererError(const QString &message) -{ - QTC_ASSERT(d->state == TestingPorts, return); - - emit errorMessage(Tr::tr("Error gathering ports: %1").arg(message) + QLatin1Char('\n')); - setFinished(TestFailure); -} - -void GenericLinuxDeviceTester::handlePortsGathererDone() -{ - QTC_ASSERT(d->state == TestingPorts, return); - - if (d->portsGatherer.usedPorts().isEmpty()) { - emit progressMessage(Tr::tr("All specified ports are available.") + QLatin1Char('\n')); - } else { - const QString portList = transform(d->portsGatherer.usedPorts(), [](const Port &port) { - return QString::number(port.number()); - }).join(", "); - emit errorMessage(Tr::tr("The following specified ports are currently in use: %1") - .arg(portList) + QLatin1Char('\n')); - } - - testFileTransfer(FileTransferMethod::Sftp); -} - -void GenericLinuxDeviceTester::testFileTransfer(FileTransferMethod method) -{ - switch (method) { - case FileTransferMethod::Sftp: d->state = TestingSftp; break; - case FileTransferMethod::Rsync: d->state = TestingRsync; break; - case FileTransferMethod::GenericCopy: QTC_CHECK(false) /* not tested */; break; - } - emit progressMessage(Tr::tr("Checking whether \"%1\" works...") - .arg(FileTransfer::transferMethodName(method))); - - d->fileTransfer.setTransferMethod(method); - d->fileTransfer.setTestDevice(d->device); - d->fileTransfer.test(); -} - -void GenericLinuxDeviceTester::handleFileTransferDone(const ProcessResultData &resultData) -{ - QTC_ASSERT(d->state == TestingSftp || d->state == TestingRsync, return); - - bool succeeded = false; - QString error; - const QString methodName = FileTransfer::transferMethodName(d->fileTransfer.transferMethod()); - if (resultData.m_error == QProcess::FailedToStart) { - error = Tr::tr("Failed to start \"%1\": %2\n").arg(methodName, resultData.m_errorString); - } else if (resultData.m_exitStatus == QProcess::CrashExit) { - error = Tr::tr("\"%1\" crashed.\n").arg(methodName); - } else if (resultData.m_exitCode != 0) { - error = Tr::tr("\"%1\" failed with exit code %2: %3\n") - .arg(methodName).arg(resultData.m_exitCode).arg(resultData.m_errorString); - } else { - succeeded = true; - } - - if (succeeded) - emit progressMessage(Tr::tr("\"%1\" is functional.\n").arg(methodName)); - else - emit errorMessage(error); - - if (d->state == TestingSftp) { - d->sftpWorks = succeeded; - testFileTransfer(FileTransferMethod::Rsync); - } else { - if (!succeeded) { - if (d->sftpWorks) { - emit progressMessage(Tr::tr("SFTP will be used for deployment, because rsync " - "is not available.\n")); - } else { - emit errorMessage(Tr::tr("Deployment to this device will not work out of the box.\n")); - } - } - d->device->setExtraData(Constants::SupportsRSync, succeeded); - if (d->sftpWorks || succeeded) - testCommands(); - else - setFinished(TestFailure); - } -} - -void GenericLinuxDeviceTester::testCommands() -{ - d->state = TestingCommands; - emit progressMessage(Tr::tr("Checking if required commands are available...")); - - d->currentCommandIndex = 0; - d->commandFailed = false; - testNextCommand(); -} - -void GenericLinuxDeviceTester::testNextCommand() -{ - d->commandsProcess.close(); - if (s_commandsToTest.size() == d->currentCommandIndex) { - setFinished(d->commandFailed ? TestFailure : TestSuccess); - return; - } - - const QString commandName = s_commandsToTest[d->currentCommandIndex]; - emit progressMessage(Tr::tr("%1...").arg(commandName)); - CommandLine command{d->device->filePath("/bin/sh"), {"-c"}}; - command.addArgs(QLatin1String("\"command -v %1\"").arg(commandName), CommandLine::Raw); - d->commandsProcess.setCommand(command); - d->commandsProcess.start(); -} - -void GenericLinuxDeviceTester::handleCommandDone() -{ - QTC_ASSERT(d->state == TestingCommands, return); - - const QString command = s_commandsToTest[d->currentCommandIndex]; - if (d->commandsProcess.result() == ProcessResult::FinishedWithSuccess) { - emit progressMessage(Tr::tr("%1 found.").arg(command)); - } else { - d->commandFailed = true; - const QString message = d->commandsProcess.result() == ProcessResult::StartFailed - ? Tr::tr("An error occurred while checking for %1.").arg(command) - + '\n' + d->commandsProcess.errorString() - : Tr::tr("%1 not found.").arg(command); - emit errorMessage(message); - } - - ++d->currentCommandIndex; - testNextCommand(); -} - -void GenericLinuxDeviceTester::setFinished(TestResult result) -{ - d->state = Inactive; - emit finished(result); + QTC_ASSERT(d->m_taskTree, return); + d->m_taskTree.reset(); + emit finished(TestFailure); } } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/linuxdevicetester.h b/src/plugins/remotelinux/linuxdevicetester.h index a2b4b0135d9..958162d6f6b 100644 --- a/src/plugins/remotelinux/linuxdevicetester.h +++ b/src/plugins/remotelinux/linuxdevicetester.h @@ -7,9 +7,6 @@ #include -namespace ProjectExplorer { enum class FileTransferMethod; } -namespace Utils { class ProcessResultData; } - namespace RemoteLinux { namespace Internal { class GenericLinuxDeviceTesterPrivate; } @@ -26,25 +23,6 @@ public: void stopTest() override; private: - void testEcho(); - void handleEchoDone(); - - void testUname(); - void handleUnameDone(); - - void testPortsGatherer(); - void handlePortsGathererError(const QString &message); - void handlePortsGathererDone(); - - void testFileTransfer(ProjectExplorer::FileTransferMethod method); - void handleFileTransferDone(const Utils::ProcessResultData &resultData); - - void testCommands(); - void testNextCommand(); - void handleCommandDone(); - - void setFinished(ProjectExplorer::DeviceTester::TestResult result); - std::unique_ptr d; };