From a50e2a824c27af0b669cd728eb901db7f6a11b72 Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Wed, 12 Feb 2014 16:34:47 +0200 Subject: [PATCH] Introducing winrtrunner winrtrunner is a console application for launching WinRT Qt packages. It handles: - installing (registering) & removing the package - launching & terminating the app - fetching & dispaying test output for test cases It is designed to compile everywhere, as the platform-specific backends are left out where not supported. Currently it only has a backend for Appx packages, so MSVC2012/2013 is required. It may support other backends over time, such as Windows Phone and remote Appx, as well as any host SDK that gains support for deploying to these platforms. Done-with: Maurice Kalinowski Change-Id: I424c228435d8eb4608a1d1854106b9df69ca5f11 Reviewed-by: Friedemann Kleint Reviewed-by: Oliver Wolff --- src/src.pro | 2 +- src/winrtrunner/appxengine.cpp | 674 ++++++++++++++++++++++++++++++++++++++++ src/winrtrunner/appxengine.h | 83 +++++ src/winrtrunner/main.cpp | 273 ++++++++++++++++ src/winrtrunner/runner.cpp | 265 ++++++++++++++++ src/winrtrunner/runner.h | 86 +++++ src/winrtrunner/runnerengine.h | 67 ++++ src/winrtrunner/winrtrunner.pro | 18 ++ 8 files changed, 1467 insertions(+), 1 deletion(-) create mode 100644 src/winrtrunner/appxengine.cpp create mode 100644 src/winrtrunner/appxengine.h create mode 100644 src/winrtrunner/main.cpp create mode 100644 src/winrtrunner/runner.cpp create mode 100644 src/winrtrunner/runner.h create mode 100644 src/winrtrunner/runnerengine.h create mode 100644 src/winrtrunner/winrtrunner.pro diff --git a/src/src.pro b/src/src.pro index 866a199..c12ff60 100644 --- a/src/src.pro +++ b/src/src.pro @@ -14,7 +14,7 @@ qtHaveModule(widgets) { } } -SUBDIRS += linguist +SUBDIRS += linguist winrtrunner !android|android_app: SUBDIRS += qtpaths mac { diff --git a/src/winrtrunner/appxengine.cpp b/src/winrtrunner/appxengine.cpp new file mode 100644 index 0000000..7d0ffda --- /dev/null +++ b/src/winrtrunner/appxengine.cpp @@ -0,0 +1,674 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "appxengine.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Management::Deployment; +using namespace ABI::Windows::ApplicationModel; + +QT_USE_NAMESPACE + +#define wchar(str) reinterpret_cast(str.utf16()) +#define hStringFromQString(str) HStringReference(reinterpret_cast(str.utf16())).Get() + +// Set a break handler for gracefully breaking long-running ops +static bool g_ctrlReceived = false; +static bool g_handleCtrl = false; +static BOOL WINAPI ctrlHandler(DWORD type) +{ + switch (type) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + g_ctrlReceived = g_handleCtrl; + return g_handleCtrl; + case CTRL_BREAK_EVENT: + case CTRL_SHUTDOWN_EVENT: + default: + break; + } + return false; +} + +class AppxEnginePrivate +{ +public: + Runner *runner; + bool hasFatalError; + + QString manifest; + QString packageFullName; + QString packageFamilyName; + QString executable; + qint64 pid; + HANDLE processHandle; + DWORD exitCode; + + ComPtr packageManager; + ComPtr uriFactory; + ComPtr appLauncher; + ComPtr packageDebug; +}; + +class XmlStream : public RuntimeClass, IStream> +{ +public: + XmlStream(const QString &fileName) + : m_manifest(fileName) + { + m_manifest.open(QFile::ReadOnly); + } + + ~XmlStream() + { + } + + HRESULT __stdcall Read(void *data, ULONG count, ULONG *bytesRead) + { + *bytesRead = m_manifest.read(reinterpret_cast(data), count); + return S_OK; + } + + HRESULT __stdcall Seek(LARGE_INTEGER pos, DWORD start, ULARGE_INTEGER *newPos) + { + switch (start) { + default: + case STREAM_SEEK_SET: + /* pos.QuadPart += 0; */ + break; + case STREAM_SEEK_CUR: + pos.QuadPart += m_manifest.pos(); + break; + case STREAM_SEEK_END: + pos.QuadPart += m_manifest.size(); + break; + } + if (!m_manifest.seek(pos.QuadPart)) + return STG_E_INVALIDPOINTER; + if (newPos) { + ULARGE_INTEGER newPosInt = { m_manifest.pos() }; + *newPos = newPosInt; + } + return S_OK; + } + + HRESULT __stdcall Stat(STATSTG *stats, DWORD flags) + { + QFileInfo info(m_manifest); + // ms to 100-ns units + ULARGE_INTEGER lastModifiedInt = { info.lastModified().toMSecsSinceEpoch() * 10000 }; + ULARGE_INTEGER createdInt = { info.created().toMSecsSinceEpoch() * 10000 }; + ULARGE_INTEGER lastReadInt = { info.lastRead().toMSecsSinceEpoch() * 10000 }; + STATSTG newStats = { + flags == STATFLAG_NONAME ? NULL : reinterpret_cast(m_manifest.fileName().data()), + STGTY_STREAM, { m_manifest.size() }, + { lastModifiedInt.u.LowPart, lastModifiedInt.u.HighPart }, + { createdInt.u.LowPart, createdInt.u.HighPart }, + { lastReadInt.u.LowPart, lastReadInt.u.HighPart }, + 0, 0, CLSID_NULL, 0, 0 + }; + *stats = newStats; + return S_OK; + } + + // Unimplemented methods + HRESULT __stdcall Write(const void *, ULONG, ULONG *) { return E_NOTIMPL; } + HRESULT __stdcall SetSize(ULARGE_INTEGER) { return E_NOTIMPL; } + HRESULT __stdcall CopyTo(IStream *, ULARGE_INTEGER, ULARGE_INTEGER *, ULARGE_INTEGER *) { return E_NOTIMPL; } + HRESULT __stdcall Commit(DWORD) { return E_NOTIMPL; } + HRESULT __stdcall Revert() { return E_NOTIMPL; } + HRESULT __stdcall LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; } + HRESULT __stdcall UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; } + HRESULT __stdcall Clone(IStream **) { return E_NOTIMPL; } + +private: + QFile m_manifest; +}; + +static bool getManifestFile(const QString &fileName, QString *manifest = 0) +{ + if (!QFile::exists(fileName)) { + qCWarning(lcWinRtRunner) << fileName << "does not exist."; + return false; + } + + // If it looks like an appx manifest, we're done + if (fileName.endsWith(QStringLiteral("AppxManifest.xml"))) { + + if (manifest) + *manifest = fileName; + return true; + } + + // If it looks like an executable, check that manifest is next to it + if (fileName.endsWith(QStringLiteral(".exe"))) { + QDir appDir = QFileInfo(fileName).absoluteDir(); + QString manifestFileName = appDir.absoluteFilePath(QStringLiteral("AppxManifest.xml")); + if (!QFile::exists(manifestFileName)) { + qCWarning(lcWinRtRunner) << manifestFileName << "does not exist."; + return false; + } + + if (manifest) + *manifest = manifestFileName; + return true; + } + + // TODO: handle already-built package as well + + qCWarning(lcWinRtRunner) << "Appx: unable to determine manifest for" << fileName << "."; + return false; +} + +bool AppxEngine::canHandle(Runner *runner) +{ + return getManifestFile(runner->app()); +} + +RunnerEngine *AppxEngine::create(Runner *runner) +{ + AppxEngine *engine = new AppxEngine(runner); + if (engine->d_ptr->hasFatalError) { + delete engine; + return 0; + } + return engine; +} + +QStringList AppxEngine::deviceNames() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + return QStringList(QStringLiteral("local")); +} + +AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) +{ + Q_D(AppxEngine); + d->runner = runner; + d->hasFatalError = false; + d->processHandle = NULL; + d->pid = -1; + d->exitCode = UINT_MAX; + + if (!getManifestFile(runner->app(), &d->manifest)) { + qCWarning(lcWinRtRunner) << "Unable to determine manifest file from" << runner->app(); + d->hasFatalError = true; + return; + } + + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to initialize COM. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(), + &d->packageManager); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate package manager. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Uri).Get(), + IID_PPV_ARGS(&d->uriFactory)); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate URI factory. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER, + IID_IApplicationActivationManager, &d->appLauncher); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate application activation manager. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + hr = CoCreateInstance(CLSID_PackageDebugSettings, nullptr, CLSCTX_INPROC_SERVER, + IID_IPackageDebugSettings, &d->packageDebug); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate package debug settings. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + ComPtr packageFactory; + hr = CoCreateInstance(CLSID_AppxFactory, nullptr, CLSCTX_INPROC_SERVER, + IID_IAppxFactory, &packageFactory); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate package factory. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + ComPtr manifestStream = Make(d->manifest); + ComPtr manifestReader; + hr = packageFactory->CreateManifestReader(manifestStream.Get(), &manifestReader); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate manifest reader. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + // ### TODO: read detailed error from event log directly + if (hr == APPX_E_INVALID_MANIFEST) { + qCWarning(lcWinRtRunner) << "More information on the error can " + "be found in the event log under " + "Microsoft\\Windows\\AppxPackagingOM"; + } + d->hasFatalError = true; + return; + } + + ComPtr packageId; + hr = manifestReader->GetPackageId(&packageId); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to obtain the package ID from the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + LPWSTR packageFullName; + hr = packageId->GetPackageFullName(&packageFullName); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to obtain the package full name from the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + d->packageFullName = QString::fromWCharArray(packageFullName); + CoTaskMemFree(packageFullName); + + LPWSTR packageFamilyName; + hr = packageId->GetPackageFamilyName(&packageFamilyName); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to obtain the package full family name from the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + d->packageFamilyName = QString::fromWCharArray(packageFamilyName); + CoTaskMemFree(packageFamilyName); + + ComPtr applications; + hr = manifestReader->GetApplications(&applications); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to get a list of applications from the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + BOOL hasCurrent; + hr = applications->GetHasCurrent(&hasCurrent); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to iterate over applications in the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + // For now, we are only interested in the first application + ComPtr application; + hr = applications->GetCurrent(&application); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to access the first application in the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + + LPWSTR executable; + application->GetStringValue(L"Executable", &executable); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to retrieve the application executable from the manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + d->hasFatalError = true; + return; + } + d->executable = QFileInfo(d->manifest).absoluteDir() + .absoluteFilePath(QString::fromWCharArray(executable)); + CoTaskMemFree(executable); + + // Set a break handler for gracefully exiting from long-running operations + SetConsoleCtrlHandler(&ctrlHandler, true); +} + +AppxEngine::~AppxEngine() +{ + Q_D(const AppxEngine); + CloseHandle(d->processHandle); +} + +bool AppxEngine::install(bool removeFirst) +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr packageInformation; + HRESULT hr = d->packageManager->FindPackageByUserSecurityIdPackageFullName( + NULL, hStringFromQString(d->packageFullName), &packageInformation); + if (SUCCEEDED(hr) && packageInformation) { + qCWarning(lcWinRtRunner) << "Package already installed."; + if (removeFirst) + remove(); + else + return true; + } + + const QString appPath = QDir::toNativeSeparators(QFileInfo(d->manifest).absoluteFilePath()); + ComPtr uri; + hr = d->uriFactory->CreateUri(hStringFromQString(appPath), &uri); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to create a URI for the package manifest. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + + return false; + } + + ComPtr> deploymentOperation; + hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0, DeploymentOptions_DevelopmentMode, &deploymentOperation); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to start package registration. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + + ComPtr results; + while ((hr = deploymentOperation->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) + Sleep(1); + + HRESULT errorCode; + hr = results->get_ExtendedErrorCode(&errorCode); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Unable to retrieve package registration results. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + + if (FAILED(errorCode)) { + HSTRING errorText; + if (SUCCEEDED(results->get_ErrorText(&errorText))) { + qCWarning(lcWinRtRunner) << "Unable to register package:" + << QString::fromWCharArray(WindowsGetStringRawBuffer(errorText, 0)); + } + return false; + } + + return SUCCEEDED(hr); +} + +bool AppxEngine::remove() +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr> deploymentOperation; + HRESULT hr = d->packageManager->RemovePackageAsync(hStringFromQString(d->packageFullName), &deploymentOperation); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner) << "Unable to start package removal:" << QDir::toNativeSeparators(d->manifest); + return false; + } + + ComPtr results; + while ((hr = deploymentOperation.Get()->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) + Sleep(1); + + if (FAILED(hr)) { + qCWarning(lcWinRtRunner) << "Unable to remove package:" << QDir::toNativeSeparators(d->manifest); + return false; + } + + return SUCCEEDED(hr); +} + +bool AppxEngine::start() +{ + Q_D(AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + const QString launchArguments = d->runner->arguments().join(QLatin1Char(' ')); + DWORD pid; + const QString activationId = d->packageFamilyName + QStringLiteral("!App"); + HRESULT hr = d->appLauncher->ActivateApplication(wchar(activationId), + wchar(launchArguments), AO_NONE, &pid); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to activate application. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + d->pid = qint64(pid); + CloseHandle(d->processHandle); + d->processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, pid); + + return true; +} + +bool AppxEngine::suspend() +{ + Q_D(AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + HRESULT hr = d->packageDebug->Suspend(wchar(d->packageFullName)); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to suspend application. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + + return true; +} + +bool AppxEngine::waitForFinished(int secs) +{ + Q_D(AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + g_handleCtrl = true; + int time = 0; + forever { + PACKAGE_EXECUTION_STATE state; + HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to get package execution state. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + qCDebug(lcWinRtRunner) << "Current execution state:" << state; + if (state != PES_RUNNING) + break; + + ++time; + if ((secs && time > secs) || g_ctrlReceived) { + g_handleCtrl = false; + return false; + } + + Sleep(1000); // Wait one second between checks + qCDebug(lcWinRtRunner) << "Waiting for app to quit - msecs to go:" << secs - time; + } + g_handleCtrl = false; + + if (!GetExitCodeProcess(d->processHandle, &d->exitCode)) + d->exitCode = UINT_MAX; + + return true; +} + +bool AppxEngine::stop() +{ + Q_D(AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // ### We won't have a process handle if we didn't start the app. We can look it up + // using a process snapshot, or by calling start if we know the process is already running. + // For now, simply continue normally, but don't fetch the exit code. + if (!d->processHandle) + qCDebug(lcWinRtRunner) << "No handle to the process; the exit code won't be available."; + + if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) { + d->exitCode = UINT_MAX; + qCWarning(lcWinRtRunner).nospace() << "Failed to obtain process exit code."; + qCDebug(lcWinRtRunner, "GetLastError: 0x%x", GetLastError()); + return false; + } + + if (!d->processHandle || d->exitCode == STILL_ACTIVE) { + HRESULT hr = d->packageDebug->TerminateAllProcesses(wchar(d->packageFullName)); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner).nospace() << "Failed to terminate package process. (0x" + << QByteArray::number(hr, 16).constData() + << ' ' << qt_error_string(hr) << ')'; + return false; + } + + if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) + d->exitCode = UINT_MAX; + } + + return true; +} + +qint64 AppxEngine::pid() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->pid; +} + +int AppxEngine::exitCode() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->exitCode == UINT_MAX ? -1 : HRESULT_CODE(d->exitCode); +} + +QString AppxEngine::executable() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->executable; +} + +QString AppxEngine::devicePath(const QString &relativePath) const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Return a path safe for passing to the application + QDir localAppDataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + const QString path = localAppDataPath.absoluteFilePath( + QStringLiteral("Packages/") + d->packageFamilyName + + QStringLiteral("/LocalState/") + relativePath); + return QDir::toNativeSeparators(path); +} + +bool AppxEngine::sendFile(const QString &localFile, const QString &deviceFile) +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Both files are local, just use QFile + QFile source(localFile); + + // Remove the destination, or copy will fail + if (QFileInfo(source) != QFileInfo(deviceFile)) + QFile::remove(deviceFile); + + bool result = source.copy(deviceFile); + if (!result) + qCWarning(lcWinRtRunner) << "Unable to sendFile:" << source.errorString(); + + return result; +} + +bool AppxEngine::receiveFile(const QString &deviceFile, const QString &localFile) +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Both files are local, so just reverse the sendFile arguments + return sendFile(deviceFile, localFile); +} diff --git a/src/winrtrunner/appxengine.h b/src/winrtrunner/appxengine.h new file mode 100644 index 0000000..1a78937 --- /dev/null +++ b/src/winrtrunner/appxengine.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef APPXENGINE_H +#define APPXENGINE_H + +#include "runnerengine.h" +#include "runner.h" + +#include +#include + +QT_USE_NAMESPACE + +class AppxEnginePrivate; +class AppxEngine : public RunnerEngine +{ +public: + static bool canHandle(Runner *runner); + static RunnerEngine *create(Runner *runner); + static QStringList deviceNames(); + + bool install(bool removeFirst = false) Q_DECL_OVERRIDE; + bool remove() Q_DECL_OVERRIDE; + bool start() Q_DECL_OVERRIDE; + bool suspend() Q_DECL_OVERRIDE; + bool waitForFinished(int secs) Q_DECL_OVERRIDE; + bool stop() Q_DECL_OVERRIDE; + qint64 pid() const Q_DECL_OVERRIDE; + int exitCode() const Q_DECL_OVERRIDE; + + QString executable() const Q_DECL_OVERRIDE; + QString devicePath(const QString &relativePath) const Q_DECL_OVERRIDE; + bool sendFile(const QString &localFile, const QString &deviceFile) Q_DECL_OVERRIDE; + bool receiveFile(const QString &deviceFile, const QString &localFile) Q_DECL_OVERRIDE; + +private: + explicit AppxEngine(Runner *runner); + ~AppxEngine(); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(AppxEngine) +}; + +#endif // APPXENGINE_H diff --git a/src/winrtrunner/main.cpp b/src/winrtrunner/main.cpp new file mode 100644 index 0000000..56179a4 --- /dev/null +++ b/src/winrtrunner/main.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "runner.h" + +QT_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QCommandLineParser parser; + parser.setApplicationDescription(QLatin1String("winrtrunner installs, runs, and collects test " + "results for packages made with Qt.")); + parser.addPositionalArgument(QStringLiteral("package [arguments]"), + QLatin1String("The executable or package manifest to act upon. " + "Arguments after the package name will be passed " + "to the application when it starts.")); + + QCommandLineOption testOption(QStringLiteral("test"), + QLatin1String("Install, start, collect output, stop (if needed), " + "and uninstall the package. This is the " + "default action of winrtrunner.")); + parser.addOption(testOption); + + QCommandLineOption startOption(QStringLiteral("start"), + QLatin1String("Start the package. The package is installed if " + "it is not already installed. Pass --install to " + "force reinstallation.")); + parser.addOption(startOption); + + QCommandLineOption suspendOption(QStringLiteral("suspend"), + QLatin1String("Suspend a running package. When combined " + "with --stop or --test, the app will be " + "suspended before being terminated.")); + parser.addOption(suspendOption); + + QCommandLineOption stopOption(QStringLiteral("stop"), + QLatin1String("Terminate a running package. Can be be " + "combined with --start and --suspend.")); + parser.addOption(stopOption); + + QCommandLineOption waitOption(QStringLiteral("wait"), + QLatin1String("If the package is running, waits the given " + "number of seconds before continuing to the next " + "task. Passing 0 causes the runner to wait " + "indefinitely."), + QStringLiteral("seconds")); + parser.addOption(waitOption); + + QCommandLineOption installOption(QStringLiteral("install"), + QStringLiteral("(Re)installs the package.")); + parser.addOption(installOption); + + QCommandLineOption removeOption(QStringLiteral("remove"), + QStringLiteral("Uninstalls the package.")); + parser.addOption(removeOption); + + QCommandLineOption deviceOption(QStringLiteral("device"), + QLatin1String("Specifies the device to target as a device name " + " or index. Use --list-devices to find available " + "devices. The default device is the first device " + "found for the active run profile."), + QStringLiteral("name|index")); + parser.addOption(deviceOption); + + QCommandLineOption profileOption(QStringLiteral("profile"), + QStringLiteral("Force a particular run profile."), + QStringLiteral("name")); + parser.addOption(profileOption); + + QCommandLineOption listDevicesOption(QStringLiteral("list-devices"), + QLatin1String("List the available devices " + "(for use with --device).")); + parser.addOption(listDevicesOption); + + QCommandLineOption verbosityOption(QStringLiteral("verbose"), + QLatin1String("The verbosity level of the message output " + "(0 - silent, 1 - info, 2 - debug). Defaults to 1."), + QStringLiteral("level"), QStringLiteral("1")); + parser.addOption(verbosityOption); + + QCommandLineOption ignoreErrorsOption(QStringLiteral("ignore-errors"), + QStringLiteral("Always exit with code 0, regardless of the error state.")); + parser.addOption(ignoreErrorsOption); + + parser.addHelpOption(); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + QStringList arguments = QCoreApplication::arguments(); + parser.parse(arguments); + + QStringList filterRules = QStringList() // Default logging rules + << QStringLiteral("qt.winrtrunner.warning=true") + << QStringLiteral("qt.winrtrunner.critical=true"); + if (parser.isSet(verbosityOption)) { + bool ok; + uint verbosity = parser.value(verbosityOption).toUInt(&ok); + if (!ok || verbosity > 2) { + qCCritical(lcWinRtRunner) << "Incorrect value specified for verbosity."; + parser.showHelp(1); + } + switch (verbosity) { + case 2: // Enable debug print + filterRules.append(QStringLiteral("qt.winrtrunner.debug=true")); + break; + case 1: // Remove warnings + filterRules.removeFirst(); + // fall through + case 0: // Silent + filterRules.removeFirst(); + // fall through + default: // Impossible + break; + } + } + QLoggingCategory::setFilterRules(filterRules.join(QLatin1Char('\n'))); + + if (parser.isSet(listDevicesOption)) { + std::wcout << "Available devices:\n"; + const QMap deviceNames = Runner::deviceNames(); + foreach (const QString &profile, deviceNames.keys()) { + std::wcout << reinterpret_cast(profile.utf16()) << ":\n"; + int index = 0; + foreach (const QString &device, deviceNames.value(profile)) { + std::wcout << " " << index++ << ' ' + << reinterpret_cast(device.utf16()) << '\n'; + } + } + std::wcout << std::endl; + return 0; + } + + // Process front-end args + if (parser.positionalArguments().count() < 1) + parser.showHelp(parser.isSet(QStringLiteral("help")) ? 0 : 1); + const QString app = parser.positionalArguments().first(); + const int appArgsPos = arguments.indexOf(app) + 1; + const QStringList mainArgs = arguments.mid(0, appArgsPos); + QStringList appArgs = arguments.mid(appArgsPos); + parser.process(mainArgs); + + // Exit codes: + // 1 - Bad arguments + // 2 - Bad package or no backend available + // 3 - Installation failed + // 4 - Removal failed + // 5 - Start failed + // 6 - Suspend failed + // 7 - Stop failed + // 8 - Test setup failed + // 9 - Test results retrieval failed + // In "test" mode, the exit code of the app is returned + + bool ignoreErrors = parser.isSet(ignoreErrorsOption); + bool testEnabled = parser.isSet(testOption); + bool startEnabled = testEnabled || parser.isSet(startOption); + bool suspendEnabled = parser.isSet(suspendOption); + bool waitEnabled = testEnabled || parser.isSet(waitOption); + bool stopEnabled = !testEnabled && parser.isSet(stopOption); // test and stop are mutually exclusive + bool installEnabled = testEnabled || startEnabled || parser.isSet(installOption); + bool removeBeforeInstall = testEnabled || parser.isSet(installOption); + bool removeEnabled = testEnabled || parser.isSet(removeOption); + // Default to test mode if no conflicting arguments were passed + if (!testEnabled && !installEnabled && !startEnabled && !stopEnabled && !suspendEnabled && !removeEnabled) + testEnabled = installEnabled = removeBeforeInstall = startEnabled = waitEnabled = stopEnabled = removeEnabled = true; + + int waitTime = parser.value(waitOption).toInt(); + if (!waitTime && testEnabled) + waitTime = 300; // The maximum wait period for test cases is 300 seconds (5 minutes) + + // Set up runner + Runner runner(app, appArgs, parser.value(profileOption), parser.value(deviceOption)); + if (!runner.isValid()) + return ignoreErrors ? 0 : 2; + + if (testEnabled && !runner.setupTest()) { + qCDebug(lcWinRtRunner) << "Test setup failed, exiting with code 8."; + return ignoreErrors ? 0 : 8; + } + + if (installEnabled && !runner.install(removeBeforeInstall)) { + qCDebug(lcWinRtRunner) << "Installation failed, exiting with code 3."; + return ignoreErrors ? 0 : 3; + } + + if (startEnabled && !runner.start()) { + qCDebug(lcWinRtRunner) << "Start failed, exiting with code 5."; + return ignoreErrors ? 0 : 5; + } + + qint64 pid = runner.pid(); + if (pid != -1) + qCWarning(lcWinRtRunner) << "App started with process ID" << pid; + + if (waitEnabled) + runner.wait(waitTime); + + if (suspendEnabled && !runner.suspend()) { + qCDebug(lcWinRtRunner) << "Suspend failed, exiting with code 6."; + return ignoreErrors ? 0 : 6; + } + + if (stopEnabled && !runner.stop()) { + qCDebug(lcWinRtRunner) << "Stop failed, exiting with code 7."; + return ignoreErrors ? 0 : 7; + } + + if (testEnabled && !runner.collectTest()) { + qCDebug(lcWinRtRunner) << "Collect test failed, exiting with code 9."; + return ignoreErrors ? 0 : 9; + } + + if (removeEnabled && !runner.remove()) { + qCDebug(lcWinRtRunner) << "Remove failed, exiting with code 4."; + return ignoreErrors ? 0 : 4; + } + + if (stopEnabled) { + int exitCode = runner.exitCode(); + if (exitCode == -1) + return 0; // Exit code unknown; not necessarily an error + qCWarning(lcWinRtRunner) << "App exited with code" << exitCode; + return ignoreErrors ? 0 : exitCode; + } + + return 0; +} diff --git a/src/winrtrunner/runner.cpp b/src/winrtrunner/runner.cpp new file mode 100644 index 0000000..86513af --- /dev/null +++ b/src/winrtrunner/runner.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "runner.h" + +#include "runnerengine.h" + +#ifndef RTRUNNER_NO_APPX +#include "appxengine.h" +#endif + +#include +#include +#include + +QT_USE_NAMESPACE + +Q_LOGGING_CATEGORY(lcWinRtRunner, "qt.winrtrunner") + +class RunnerPrivate +{ +public: + bool isValid; + QString app; + QString manifest; + QStringList arguments; + int deviceIndex; + QString deviceOutputFile; + QString localOutputFile; + + QString profile; + QScopedPointer engine; +}; + +QMap Runner::deviceNames() +{ + QMap deviceNames; +#ifndef RTRUNNER_NO_APPX + deviceNames.insert(QStringLiteral("Appx"), AppxEngine::deviceNames()); +#endif + return deviceNames; +} + +Runner::Runner(const QString &app, const QStringList &arguments, + const QString &profile, const QString &deviceName) + : d_ptr(new RunnerPrivate) +{ + Q_D(Runner); + d->isValid = false; + d->app = app; + d->arguments = arguments; + d->profile = profile; + + bool deviceIndexKnown; + d->deviceIndex = deviceName.toInt(&deviceIndexKnown); +#ifndef RTRUNNER_NO_APPX + if (!deviceIndexKnown) { + d->deviceIndex = AppxEngine::deviceNames().indexOf(deviceName); + if (d->deviceIndex < 0) + d->deviceIndex = 0; + } + if ((d->profile.isEmpty() || d->profile.toLower() == QStringLiteral("appx")) + && AppxEngine::canHandle(this)) { + if (RunnerEngine *engine = AppxEngine::create(this)) { + d->engine.reset(engine); + d->isValid = true; + qCWarning(lcWinRtRunner) << "Using the Appx profile."; + return; + } + } +#endif + // Place other engines here + + qCWarning(lcWinRtRunner) << "Unable to find a run profile for" << app << "."; +} + +Runner::~Runner() +{ +} + +bool Runner::isValid() const +{ + Q_D(const Runner); + return d->isValid; +} + +QString Runner::app() const +{ + Q_D(const Runner); + return d->app; +} + +QStringList Runner::arguments() const +{ + Q_D(const Runner); + return d->arguments; +} + +int Runner::deviceIndex() const +{ + Q_D(const Runner); + return d->deviceIndex; +} + +bool Runner::install(bool removeFirst) +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->install(removeFirst); +} + +bool Runner::remove() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->remove(); +} + +bool Runner::start() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->start(); +} + +bool Runner::suspend() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->suspend(); +} + +bool Runner::stop() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->stop(); +} + +bool Runner::wait(int maxWaitTime) +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + return d->engine->waitForFinished(maxWaitTime); +} + +bool Runner::setupTest() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + // Fix-up output path + int outputIndex = d->arguments.indexOf(QStringLiteral("-o")) + 1; + if (outputIndex > 0 && d->arguments.size() > outputIndex) { + d->localOutputFile = d->arguments.at(outputIndex); + } else { + if (outputIndex > 0) + d->arguments.removeAt(outputIndex); + d->localOutputFile = QFileInfo(d->engine->executable()).baseName() + QStringLiteral("_output.txt"); + d->arguments.append(QStringLiteral("-o")); + d->arguments.append(QString()); + outputIndex = d->arguments.length() - 1; + } + d->deviceOutputFile = d->engine->devicePath(d->localOutputFile); + d->arguments[outputIndex] = d->deviceOutputFile; + + // Write a qt.conf to the executable directory + QDir executableDir = QFileInfo(d->engine->executable()).absoluteDir(); + QFile qtConf(executableDir.absoluteFilePath(QStringLiteral("qt.conf"))); + if (!qtConf.exists()) { + if (!qtConf.open(QFile::WriteOnly)) { + qCWarning(lcWinRtRunner) << "Could not open qt.conf for writing."; + return false; + } + qtConf.write(QByteArrayLiteral("[Paths]\nPlugins=/")); + } + + return true; +} + +bool Runner::collectTest() +{ + Q_D(Runner); + Q_ASSERT(d->engine); + + // Fetch test output + if (!d->engine->receiveFile(d->deviceOutputFile, d->localOutputFile)) { + qCWarning(lcWinRtRunner).nospace() + << "Unable to copy test output file \"" << d->deviceOutputFile + << "\" to local file \"" << d->localOutputFile << "\"."; + return false; + } + + QFile testResults(d->localOutputFile); + if (!testResults.open(QFile::ReadOnly)) { + qCWarning(lcWinRtRunner) << "Unable to read test results:" << testResults.errorString(); + return false; + } + + const QByteArray contents = testResults.readAll(); + std::fputs(contents.constData(), stdout); + return true; +} + +qint64 Runner::pid() +{ + Q_D(Runner); + if (!d->engine) + return -1; + + return d->engine->pid(); +} + +int Runner::exitCode() +{ + Q_D(Runner); + if (!d->engine) + return -1; + + return d->engine->exitCode(); +} diff --git a/src/winrtrunner/runner.h b/src/winrtrunner/runner.h new file mode 100644 index 0000000..4561550 --- /dev/null +++ b/src/winrtrunner/runner.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef RUNNER_H +#define RUNNER_H + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class RunnerPrivate; +class Runner +{ +public: + static QMap deviceNames(); + + Runner(const QString &app, const QStringList &arguments, const QString &profile = QString(), + const QString &device = QString()); + ~Runner(); + + bool isValid() const; + QString app() const; + QStringList arguments() const; + int deviceIndex() const; + + bool install(bool removeFirst = false); + bool remove(); + bool start(); + bool suspend(); + bool stop(); + bool wait(int maxWaitTime = 0); + bool setupTest(); + bool collectTest(); + qint64 pid(); + int exitCode(); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(Runner) +}; + +Q_DECLARE_LOGGING_CATEGORY(lcWinRtRunner) + +#endif // RUNNER_H diff --git a/src/winrtrunner/runnerengine.h b/src/winrtrunner/runnerengine.h new file mode 100644 index 0000000..187be5f --- /dev/null +++ b/src/winrtrunner/runnerengine.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef RUNNERENGINE_H +#define RUNNERENGINE_H + +#include + +QT_USE_NAMESPACE + +class RunnerEngine +{ +public: + virtual ~RunnerEngine() { } + virtual bool install(bool removeFirst = false) = 0; + virtual bool remove() = 0; + virtual bool start() = 0; + virtual bool suspend() = 0; + virtual bool waitForFinished(int secs) = 0; + virtual bool stop() = 0; + virtual qint64 pid() const = 0; + virtual int exitCode() const = 0; + virtual QString executable() const = 0; + virtual QString devicePath(const QString &relativePath) const = 0; + virtual bool sendFile(const QString &localFile, const QString &deviceFile) = 0; + virtual bool receiveFile(const QString &deviceFile, const QString &localFile) = 0; +}; + +#endif // RUNNERENGINE_H diff --git a/src/winrtrunner/winrtrunner.pro b/src/winrtrunner/winrtrunner.pro new file mode 100644 index 0000000..28fef19 --- /dev/null +++ b/src/winrtrunner/winrtrunner.pro @@ -0,0 +1,18 @@ +option(host_build) +CONFIG += force_bootstrap + +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII WINRT_LIBRARY + +SOURCES += main.cpp runner.cpp +HEADERS += runner.h runnerengine.h + +DEFINES += RTRUNNER_NO_APPX + +win32-msvc2012|win32-msvc2013 { + SOURCES += appxengine.cpp + HEADERS += appxengine.h + LIBS += -lruntimeobject + DEFINES -= RTRUNNER_NO_APPX +} + +load(qt_tool) -- 2.7.4