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
This commit is contained in:
parent
1af31d83c2
commit
14546bfad1
@ -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';
|
|||||||
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';
|
|||||||
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 {
|
|||||||
_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';
|
|||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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,8 +251,18 @@ class WindowsUWPDevice extends Device {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> uninstallApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
final String packageFamily = await _uwptool.getPackageFamilyName(packageName);
|
||||||
|
if (packageFamily == null) {
|
||||||
|
// App is not installed.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _uwptool.uninstallApp(packageFamily);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
|
FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
|
||||||
|
@ -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() {
|
|||||||
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() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
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 {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LookupAppIdRequest {
|
@override
|
||||||
const _LookupAppIdRequest(this.packageId);
|
Future<bool> uninstallApp(String packageFamily) async {
|
||||||
|
uninstallRequests.add(_UninstallRequest(packageFamily));
|
||||||
|
isInstalled = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GetPackageFamilyRequest {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user