Unverified Commit 14546bfa authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Support uninstall, install status query for UWP (#82481)

Adds UwpTool.install and UwpTool.uninstall methods. Refactors the
PowerShell-based install code to move the powershell-related bits out of
the Device class and into UwpTool so that when we swap out the
PowerShell-based install for the uwptool-based install, it's transparent
to the WindowsUWPDevice class.

Adds implementations for:
* WindowsUWPDevice.isAppInstalled
* WindowsUWPDevice.uninstallApp

Refactors:
* WindowsUWPDevice.installApp
parent 1af31d83
......@@ -209,6 +209,7 @@ Future<T> runInContext<T>(
),
uwptool: UwpTool(
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
),
......
......@@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/process.dart';
......@@ -22,13 +23,16 @@ import '../base/process.dart';
class UwpTool {
UwpTool({
@required Artifacts artifacts,
@required FileSystem fileSystem,
@required Logger logger,
@required ProcessManager processManager,
}) : _artifacts = artifacts,
_fileSystem = fileSystem,
_logger = logger,
_processUtils = ProcessUtils(processManager: processManager, logger: logger);
final Artifacts _artifacts;
final FileSystem _fileSystem;
final Logger _logger;
final ProcessUtils _processUtils;
......@@ -44,44 +48,74 @@ class UwpTool {
_logger.printError('Failed to list installed UWP apps: ${result.stderr}');
return <String>[];
}
final List<String> appIds = <String>[];
final List<String> packageFamilies = <String>[];
for (final String line in result.stdout.toString().split('\n')) {
final String appId = line.trim();
if (appId.isNotEmpty) {
appIds.add(appId);
final String packageFamily = line.trim();
if (packageFamily.isNotEmpty) {
packageFamilies.add(packageFamily);
}
}
return appIds;
return packageFamilies;
}
/// Returns the app ID for the specified package ID.
/// Returns the package family name for the specified package name.
///
/// If no installed application on the system matches the specified GUID,
/// returns null.
Future<String/*?*/> getAppIdFromPackageId(String packageId) async {
for (final String appId in await listApps()) {
if (appId.startsWith(packageId)) {
return appId;
/// If no installed application on the system matches the specified package
/// name, returns null.
Future<String/*?*/> getPackageFamilyName(String packageName) async {
for (final String packageFamily in await listApps()) {
if (packageFamily.startsWith(packageName)) {
return packageFamily;
}
}
return null;
}
/// Launches the app with the specified app ID.
/// Launches the app with the specified package family name.
///
/// On success, returns the process ID of the launched app, otherwise null.
Future<int/*?*/> launchApp(String appId, List<String> args) async {
Future<int/*?*/> launchApp(String packageFamily, List<String> args) async {
final List<String> launchCommand = <String>[
_binaryPath,
'launch',
appId
packageFamily
] + args;
final RunResult result = await _processUtils.run(launchCommand);
if (result.exitCode != 0) {
_logger.printError('Failed to launch app $appId: ${result.stderr}');
_logger.printError('Failed to launch app $packageFamily: ${result.stderr}');
return null;
}
// Read the process ID from stdout.
return int.tryParse(result.stdout.toString().trim());
}
/// Installs the app with the specified build directory.
///
/// Returns `true` on success.
Future<bool> installApp(String buildDirectory) async {
final List<String> launchCommand = <String>[
'powershell.exe',
_fileSystem.path.join(buildDirectory, 'install.ps1'),
];
final RunResult result = await _processUtils.run(launchCommand);
if (result.exitCode != 0) {
_logger.printError(result.stdout.toString());
_logger.printError(result.stderr.toString());
}
return result.exitCode == 0;
}
Future<bool> uninstallApp(String packageFamily) async {
final List<String> launchCommand = <String>[
_binaryPath,
'uninstall',
packageFamily
];
final RunResult result = await _processUtils.run(launchCommand);
if (result.exitCode != 0) {
_logger.printError('Failed to uninstall $packageFamily');
return false;
}
return true;
}
}
......@@ -11,7 +11,6 @@ import 'package:process/process.dart';
import '../application_package.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/utils.dart';
......@@ -144,19 +143,16 @@ class WindowsUWPDevice extends Device {
}
final String config = toTitleCase(getNameForBuildMode(_buildMode ?? BuildMode.debug));
final String generated = '${binaryName}_${packageVersion}_${config}_Test';
final ProcessResult result = await _processManager.run(<String>[
'powershell.exe',
_fileSystem.path.join('build', 'winuwp', 'runner_uwp', 'AppPackages', binaryName, generated, 'install.ps1'),
]);
if (result.exitCode != 0) {
_logger.printError(result.stdout.toString());
_logger.printError(result.stderr.toString());
}
return result.exitCode == 0;
final String buildDirectory = _fileSystem.path.join(
'build', 'winuwp', 'runner_uwp', 'AppPackages', binaryName, generated);
return _uwptool.installApp(buildDirectory);
}
@override
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async => false;
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async {
final String packageName = app.id;
return await _uwptool.getPackageFamilyName(packageName) != null;
}
@override
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app) async => false;
......@@ -193,16 +189,16 @@ class WindowsUWPDevice extends Device {
return LaunchResult.failed();
}
final String guid = package.id;
if (guid == null) {
final String packageName = package.id;
if (packageName == null) {
_logger.printError('Could not find PACKAGE_GUID in ${package.project.runnerCmakeFile.path}');
return LaunchResult.failed();
}
final String appId = await _uwptool.getAppIdFromPackageId(guid);
final String packageFamily = await _uwptool.getPackageFamilyName(packageName);
if (debuggingOptions.buildInfo.mode.isRelease) {
_processId = await _uwptool.launchApp(appId, <String>[]);
_processId = await _uwptool.launchApp(packageFamily, <String>[]);
return _processId != null ? LaunchResult.succeeded() : LaunchResult.failed();
}
......@@ -210,7 +206,7 @@ class WindowsUWPDevice extends Device {
if (_logger.terminal.stdinHasTerminal) {
await _logger.terminal.promptForCharInput(<String>['Y', 'y'], logger: _logger,
prompt: 'To continue start an admin cmd prompt and run the following command:\n'
' checknetisolation loopbackexempt -is -n=$appId\n'
' checknetisolation loopbackexempt -is -n=$packageFamily\n'
'Press "Y/y" once this is complete.'
);
}
......@@ -238,7 +234,7 @@ class WindowsUWPDevice extends Device {
if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache',
if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup',
];
_processId = await _uwptool.launchApp(appId, args);
_processId = await _uwptool.launchApp(packageFamily, args);
if (_processId == null) {
return LaunchResult.failed();
}
......@@ -255,8 +251,18 @@ class WindowsUWPDevice extends Device {
@override
Future<bool> uninstallApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
final String packageName = app.id;
if (packageName == null) {
_logger.printError('Could not find PACKAGE_GUID in ${app.project.runnerCmakeFile.path}');
return false;
}
final String packageFamily = await _uwptool.getPackageFamilyName(packageName);
if (packageFamily == null) {
// App is not installed.
return true;
}
return _uwptool.uninstallApp(packageFamily);
}
@override
FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
......
......@@ -43,13 +43,14 @@ void main() {
});
testWithoutContext('WindowsUwpDevice defaults', () async {
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice();
final FakeUwpTool uwptool = FakeUwpTool();
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(uwptool: uwptool);
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
expect(await windowsDevice.targetPlatform, TargetPlatform.windows_uwp_x64);
expect(windowsDevice.name, 'Windows (UWP)');
expect(await windowsDevice.installApp(package), true);
expect(await windowsDevice.uninstallApp(package), false);
expect(await windowsDevice.uninstallApp(package), true);
expect(await windowsDevice.isLatestBuildInstalled(package), false);
expect(await windowsDevice.isAppInstalled(package), false);
expect(windowsDevice.category, Category.desktop);
......@@ -170,15 +171,8 @@ void main() {
Cache.flutterRoot = '';
final FakeUwpTool uwptool = FakeUwpTool();
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'powershell.exe',
'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Debug_Test/install.ps1',
]),
]);
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
fileSystem: fileSystem,
processManager: processManager,
uwptool: uwptool,
);
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
......@@ -191,7 +185,7 @@ void main() {
);
expect(result.started, true);
expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
expect(uwptool.launchRequests.single.packageFamily, 'PACKAGE-ID_publisher');
expect(uwptool.launchRequests.single.args, <String>[
'--observatory-port=12345',
'--disable-service-auth-codes',
......@@ -205,15 +199,8 @@ void main() {
Cache.flutterRoot = '';
final FakeUwpTool uwptool = FakeUwpTool();
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'powershell.exe',
'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Release_Test/install.ps1',
]),
]);
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
fileSystem: fileSystem,
processManager: processManager,
uwptool: uwptool,
);
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
......@@ -226,7 +213,7 @@ void main() {
);
expect(result.started, true);
expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
expect(uwptool.launchRequests.single.packageFamily, 'PACKAGE-ID_publisher');
expect(uwptool.launchRequests.single.args, <String>[]);
});
}
......@@ -279,44 +266,71 @@ class FakeBuildableUwpApp extends Fake implements BuildableUwpApp {
String get name => 'testapp';
@override
String get projectVersion => '1.2.3.4';
// Test helper to get the expected package family.
static const String packageFamily = 'PACKAGE-ID_publisher';
}
class FakeUwpTool implements UwpTool {
bool isInstalled = false;
final List<_GetPackageFamilyRequest> getPackageFamilyRequests = <_GetPackageFamilyRequest>[];
final List<_LaunchRequest> launchRequests = <_LaunchRequest>[];
final List<_LookupAppIdRequest> lookupAppIdRequests = <_LookupAppIdRequest>[];
final List<_InstallRequest> installRequests = <_InstallRequest>[];
final List<_UninstallRequest> uninstallRequests = <_UninstallRequest>[];
@override
Future<List<String>> listApps() async {
return <String>[
'fb89bf4f-55db-4bcd-8f0b-d8139953e08b',
'3e556a66-cb7f-4335-9569-35d5f5e37219',
'dfe5d409-a524-4635-b2f8-78a5e9551994',
'51e8a06b-02e8-4f76-9131-f20ce114fc34',
];
return isInstalled ? <String>[FakeBuildableUwpApp.packageFamily] : <String>[];
}
@override
Future<String> getAppIdFromPackageId(String packageId) async {
lookupAppIdRequests.add(_LookupAppIdRequest(packageId));
return '${packageId}_asdfghjkl';
Future<String/*?*/> getPackageFamilyName(String packageName) async {
getPackageFamilyRequests.add(_GetPackageFamilyRequest(packageName));
return isInstalled ? FakeBuildableUwpApp.packageFamily : null;
}
@override
Future<int> launchApp(String appId, List<String> args) async {
launchRequests.add(_LaunchRequest(appId, args));
Future<int/*?*/> launchApp(String packageFamily, List<String> args) async {
launchRequests.add(_LaunchRequest(packageFamily, args));
return 42;
}
@override
Future<bool> installApp(String buildDirectory) async {
installRequests.add(_InstallRequest(buildDirectory));
isInstalled = true;
return true;
}
@override
Future<bool> uninstallApp(String packageFamily) async {
uninstallRequests.add(_UninstallRequest(packageFamily));
isInstalled = false;
return true;
}
}
class _LookupAppIdRequest {
const _LookupAppIdRequest(this.packageId);
class _GetPackageFamilyRequest {
const _GetPackageFamilyRequest(this.packageId);
final String packageId;
}
class _LaunchRequest {
const _LaunchRequest(this.appId, this.args);
const _LaunchRequest(this.packageFamily, this.args);
final String appId;
final String packageFamily;
final List<String> args;
}
class _InstallRequest {
const _InstallRequest(this.buildDirectory);
final String buildDirectory;
}
class _UninstallRequest {
const _UninstallRequest(this.packageFamily);
final String packageFamily;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment