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