From 07e9ed746790ef9099987d6c7fd89abbb697e0c3 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Wed, 19 Mar 2014 17:31:32 +0100 Subject: [PATCH] winrtrunner: install missing dependencies On install, winrtrunner will check the app's dependencies, look for them in the directory of the extension SDKs and install them automatically for the current user. Task-number: QTBUG-37598 Change-Id: I5639491ee47af16618279245eed4dd94e61f58f9 Reviewed-by: Oliver Wolff --- src/winrtrunner/appxengine.cpp | 234 ++++++++++++++++++++++++++++++++++++---- src/winrtrunner/appxengine.h | 3 + src/winrtrunner/winrtrunner.pro | 2 +- 3 files changed, 219 insertions(+), 20 deletions(-) diff --git a/src/winrtrunner/appxengine.cpp b/src/winrtrunner/appxengine.cpp index 24d2ffd..039fa4f 100644 --- a/src/winrtrunner/appxengine.cpp +++ b/src/winrtrunner/appxengine.cpp @@ -43,12 +43,14 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -60,11 +62,13 @@ using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Management::Deployment; using namespace ABI::Windows::ApplicationModel; +using namespace ABI::Windows::System; QT_USE_NAMESPACE #define wchar(str) reinterpret_cast(str.utf16()) #define hStringFromQString(str) HStringReference(reinterpret_cast(str.utf16())).Get() +#define QStringFromHString(hstr) QString::fromWCharArray(WindowsGetStringRawBuffer(hstr, Q_NULLPTR)) // Set a break handler for gracefully breaking long-running ops static bool g_ctrlReceived = false; @@ -243,15 +247,21 @@ public: QString manifest; QString packageFullName; QString packageFamilyName; + ProcessorArchitecture packageArchitecture; QString executable; qint64 pid; HANDLE processHandle; DWORD exitCode; + QSet dependencies; + QSet installedPackages; ComPtr packageManager; ComPtr uriFactory; ComPtr appLauncher; + ComPtr packageFactory; ComPtr packageDebug; + + void retrieveInstalledPackages(); }; class XmlStream : public RuntimeClass, IStream> @@ -398,6 +408,22 @@ QStringList AppxEngine::deviceNames() #define CHECK_RESULT_FATAL(errorMessage, action)\ do {CHECK_RESULT(errorMessage, d->hasFatalError = true; action;);} while (false) +static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch) +{ + switch (appxArch) { + case APPX_PACKAGE_ARCHITECTURE_X86: + return ProcessorArchitecture_X86; + case APPX_PACKAGE_ARCHITECTURE_ARM: + return ProcessorArchitecture_Arm; + case APPX_PACKAGE_ARCHITECTURE_X64: + return ProcessorArchitecture_X64; + case APPX_PACKAGE_ARCHITECTURE_NEUTRAL: + // fall-through intended + default: + return ProcessorArchitecture_Neutral; + } +} + AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) { Q_D(AppxEngine); @@ -432,14 +458,13 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) IID_IPackageDebugSettings, &d->packageDebug); CHECK_RESULT_FATAL("Failed to instantiate package debug settings.", return); - ComPtr packageFactory; hr = CoCreateInstance(CLSID_AppxFactory, nullptr, CLSCTX_INPROC_SERVER, - IID_IAppxFactory, &packageFactory); + IID_IAppxFactory, &d->packageFactory); CHECK_RESULT_FATAL("Failed to instantiate package factory.", return); ComPtr manifestStream = Make(d->manifest); ComPtr manifestReader; - hr = packageFactory->CreateManifestReader(manifestStream.Get(), &manifestReader); + hr = d->packageFactory->CreateManifestReader(manifestStream.Get(), &manifestReader); if (FAILED(hr)) { qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate manifest reader. (0x" << QByteArray::number(hr, 16).constData() @@ -458,6 +483,11 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) hr = manifestReader->GetPackageId(&packageId); CHECK_RESULT_FATAL("Unable to obtain the package ID from the manifest.", return); + APPX_PACKAGE_ARCHITECTURE arch; + hr = packageId->GetArchitecture(&arch); + CHECK_RESULT_FATAL("Failed to retrieve the app's architecture.", return); + d->packageArchitecture = toProcessorArchitecture(arch); + LPWSTR packageFullName; hr = packageId->GetPackageFullName(&packageFullName); CHECK_RESULT_FATAL("Unable to obtain the package full name from the manifest.", return); @@ -490,6 +520,27 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) .absoluteFilePath(QString::fromWCharArray(executable)); CoTaskMemFree(executable); + d->retrieveInstalledPackages(); + + ComPtr dependencies; + hr = manifestReader->GetPackageDependencies(&dependencies); + CHECK_RESULT_FATAL("Failed to retrieve the package dependencies from the manifest.", return); + + hr = dependencies->GetHasCurrent(&hasCurrent); + CHECK_RESULT_FATAL("Failed to iterate over dependencies in the manifest.", return); + while (SUCCEEDED(hr) && hasCurrent) { + ComPtr dependency; + hr = dependencies->GetCurrent(&dependency); + CHECK_RESULT_FATAL("Failed to access dependency in the manifest.", return); + + LPWSTR name; + hr = dependency->GetName(&name); + CHECK_RESULT_FATAL("Failed to access dependency name.", return); + d->dependencies.insert(QString::fromWCharArray(name)); + CoTaskMemFree(name); + hr = dependencies->MoveNext(&hasCurrent); + } + // Set a break handler for gracefully exiting from long-running operations SetConsoleCtrlHandler(&ctrlHandler, true); } @@ -500,30 +551,111 @@ AppxEngine::~AppxEngine() CloseHandle(d->processHandle); } -bool AppxEngine::install(bool removeFirst) +bool AppxEngine::installDependencies() { - Q_D(const AppxEngine); + Q_D(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; + QSet toInstall; + foreach (const QString &dependencyName, d->dependencies) { + if (d->installedPackages.contains(dependencyName)) + continue; + toInstall.insert(dependencyName); + qCDebug(lcWinRtRunner).nospace() + << "dependency to be installed: " << dependencyName; + } + + if (toInstall.isEmpty()) + return true; + + const QByteArray extensionSdkDirRaw = qgetenv("ExtensionSdkDir"); + if (extensionSdkDirRaw.isEmpty()) { + qCWarning(lcWinRtRunner).nospace() + << QStringLiteral("The environment variable ExtensionSdkDir is not set."); + return false; + } + const QString extensionSdkDir = QString::fromLocal8Bit(extensionSdkDirRaw); + if (!QFile::exists(extensionSdkDir)) { + qCWarning(lcWinRtRunner).nospace() + << QString(QStringLiteral("The directory '%1' does not exist.")).arg( + QDir::toNativeSeparators(extensionSdkDir)); + return false; + } + qCDebug(lcWinRtRunner).nospace() + << "looking for dependency packages in " << extensionSdkDir; + QDirIterator dit(extensionSdkDir, QStringList() << QStringLiteral("*.appx"), + QDir::Files, + QDirIterator::Subdirectories); + while (dit.hasNext()) { + dit.next(); + + HRESULT hr; + ComPtr inputStream; + hr = SHCreateStreamOnFileEx(wchar(dit.filePath()), + STGM_READ | STGM_SHARE_EXCLUSIVE, + 0, FALSE, NULL, &inputStream); + CHECK_RESULT("Failed to create input stream for package in ExtensionSdkDir.", continue); + + ComPtr packageReader; + hr = d->packageFactory->CreatePackageReader(inputStream.Get(), &packageReader); + CHECK_RESULT("Failed to create package reader for package in ExtensionSdkDir.", continue); + + ComPtr manifestReader; + hr = packageReader->GetManifest(&manifestReader); + CHECK_RESULT("Failed to create manifest reader for package in ExtensionSdkDir.", continue); + + ComPtr packageId; + hr = manifestReader->GetPackageId(&packageId); + CHECK_RESULT("Failed to retrieve package id for package in ExtensionSdkDir.", continue); + + LPWSTR sz; + hr = packageId->GetName(&sz); + CHECK_RESULT("Failed to retrieve name from package in ExtensionSdkDir.", continue); + const QString name = QString::fromWCharArray(sz); + CoTaskMemFree(sz); + + if (!toInstall.contains(name)) + continue; + + APPX_PACKAGE_ARCHITECTURE arch; + hr = packageId->GetArchitecture(&arch); + CHECK_RESULT("Failed to retrieve architecture from package in ExtensionSdkDir.", continue); + if (d->packageArchitecture != arch) + continue; + + qCDebug(lcWinRtRunner).nospace() + << "installing dependency " << name << " from " << dit.filePath(); + if (installPackage(dit.filePath())) + toInstall.remove(name); } - const QString appPath = QDir::toNativeSeparators(QFileInfo(d->manifest).absoluteFilePath()); + return true; +} + +bool AppxEngine::installPackage(const QString &filePath) +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__ << filePath; + + const QString nativeFilePath = QDir::toNativeSeparators(QFileInfo(filePath).absoluteFilePath()); + const bool addInsteadOfRegister = nativeFilePath.endsWith(QStringLiteral(".appx"), + Qt::CaseInsensitive); + HRESULT hr; ComPtr uri; - hr = d->uriFactory->CreateUri(hStringFromQString(appPath), &uri); - CHECK_RESULT("Unable to create a URI for the package manifest.", return false); + hr = d->uriFactory->CreateUri(hStringFromQString(nativeFilePath), &uri); + CHECK_RESULT("Unable to create an URI for the package.", return false); ComPtr> deploymentOperation; - hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0, DeploymentOptions_DevelopmentMode, &deploymentOperation); - CHECK_RESULT("Unable to start package registration.", return false); + if (addInsteadOfRegister) { + hr = d->packageManager->AddPackageAsync(uri.Get(), NULL, DeploymentOptions_None, + &deploymentOperation); + CHECK_RESULT("Unable to add package.", return false); + } else { + hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0, + DeploymentOptions_DevelopmentMode, + &deploymentOperation); + CHECK_RESULT("Unable to start package registration.", return false); + } ComPtr results; while ((hr = deploymentOperation->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) @@ -556,6 +688,25 @@ bool AppxEngine::install(bool removeFirst) return SUCCEEDED(hr); } +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; + } + + return installDependencies() && installPackage(d->manifest); +} + bool AppxEngine::remove() { Q_D(const AppxEngine); @@ -757,3 +908,48 @@ bool AppxEngine::receiveFile(const QString &deviceFile, const QString &localFile // Both files are local, so just reverse the sendFile arguments return sendFile(deviceFile, localFile); } + +void AppxEnginePrivate::retrieveInstalledPackages() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr> packages; + HRESULT hr = packageManager->FindPackagesByUserSecurityId(NULL, &packages); + CHECK_RESULT("Failed to find packages.", return); + + ComPtr> pkgit; + hr = packages->First(&pkgit); + CHECK_RESULT("Failed to get package iterator.", return); + + boolean hasCurrent; + hr = pkgit->get_HasCurrent(&hasCurrent); + while (SUCCEEDED(hr) && hasCurrent) { + ComPtr pkg; + hr = pkgit->get_Current(&pkg); + CHECK_RESULT("Failed to get current package.", return); + + ComPtr pkgId; + hr = pkg->get_Id(&pkgId); + CHECK_RESULT("Failed to get package id.", return); + + HString name; + hr = pkgId->get_Name(name.GetAddressOf()); + CHECK_RESULT("Failed retrieve package name.", return); + + ProcessorArchitecture architecture; + if (packageArchitecture == ProcessorArchitecture_Neutral) { + architecture = packageArchitecture; + } else { + hr = pkgId->get_Architecture(&architecture); + CHECK_RESULT("Failed to retrieve package architecture.", return); + } + + const QString pkgName = QStringFromHString(name.Get()); + qCDebug(lcWinRtRunner) << "found installed package" << pkgName; + + if (architecture == packageArchitecture) + installedPackages.insert(pkgName); + + hr = pkgit->MoveNext(&hasCurrent); + } +} diff --git a/src/winrtrunner/appxengine.h b/src/winrtrunner/appxengine.h index 7bc3694..d930751 100644 --- a/src/winrtrunner/appxengine.h +++ b/src/winrtrunner/appxengine.h @@ -79,6 +79,9 @@ private: explicit AppxEngine(Runner *runner); ~AppxEngine(); + bool installDependencies(); + bool installPackage(const QString &filePath); + QScopedPointer d_ptr; Q_DECLARE_PRIVATE(AppxEngine) }; diff --git a/src/winrtrunner/winrtrunner.pro b/src/winrtrunner/winrtrunner.pro index d8a0620..7203fd6 100644 --- a/src/winrtrunner/winrtrunner.pro +++ b/src/winrtrunner/winrtrunner.pro @@ -11,7 +11,7 @@ DEFINES += RTRUNNER_NO_APPX RTRUNNER_NO_XAP win32-msvc2012|win32-msvc2013 { SOURCES += appxengine.cpp HEADERS += appxengine.h - LIBS += -lruntimeobject -lwsclient + LIBS += -lruntimeobject -lwsclient -lShlwapi DEFINES -= RTRUNNER_NO_APPX include(../shared/corecon/corecon.pri) -- 2.7.4