[flutter_tools] support flutter run -d winuwp (#82373)
Allow flutter run to work end-to-end with a UWP device.
Uses win32/ffi for the actual launch of the application, injected via
the native API class. This is structured to avoid a g3 dependency.
Install and amuid require powershell scripts for now.
Actually connecting to the observatory requires running a command in an
elevated prompt. Instructions are presented to the user if a terminal is
attached.
This is a rebased version of https://github.com/flutter/flutter/pull/79684
by @jonahwilliams, updated to remove `NativeApi` and replace is with calls
to `uwptool`.
Part of https://github.com/flutter/flutter/issues/82085
diff --git a/packages/flutter_tools/bin/getaumidfromname.ps1 b/packages/flutter_tools/bin/getaumidfromname.ps1
new file mode 100644
index 0000000..d5873e3
--- /dev/null
+++ b/packages/flutter_tools/bin/getaumidfromname.ps1
@@ -0,0 +1,13 @@
+# Copyright 2014 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Retrieves the AMUID from a given application name
+[CmdletBinding()]
+param(
+ [Parameter()]
+ [string]$Name
+)
+$foo = get-appxpackage | Where-Object { $_.Name -like $name }
+$aumid = $foo.packagefamilyname + "!" + (Get-AppxPackageManifest $foo).package.applications.application.id
+Write-Output $aumid
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index f5d404d..a5e7c8d 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -55,6 +55,9 @@
/// Tools related to subsetting or icon font files.
fontSubset,
constFinder,
+
+ // Windows UWP app management tool.
+ uwptool,
}
/// A subset of [Artifact]s that are platform and build mode independent
@@ -111,8 +114,31 @@
return getNameForTargetPlatform(platform);
}
+bool _isWindows(TargetPlatform platform) {
+ switch (platform) {
+ case TargetPlatform.windows_x64:
+ case TargetPlatform.windows_uwp_x64:
+ return true;
+ case TargetPlatform.android:
+ case TargetPlatform.android_arm:
+ case TargetPlatform.android_arm64:
+ case TargetPlatform.android_x64:
+ case TargetPlatform.android_x86:
+ case TargetPlatform.darwin:
+ case TargetPlatform.fuchsia_arm64:
+ case TargetPlatform.fuchsia_x64:
+ case TargetPlatform.ios:
+ case TargetPlatform.linux_arm64:
+ case TargetPlatform.linux_x64:
+ case TargetPlatform.tester:
+ case TargetPlatform.web_javascript:
+ return false;
+ }
+ return false;
+}
+
String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMode mode ]) {
- final String exe = platform == TargetPlatform.windows_x64 ? '.exe' : '';
+ final String exe = _isWindows(platform) ? '.exe' : '';
switch (artifact) {
case Artifact.genSnapshot:
return 'gen_snapshot';
@@ -162,6 +188,8 @@
return 'font-subset$exe';
case Artifact.constFinder:
return 'const_finder.dart.snapshot';
+ case Artifact.uwptool:
+ return 'uwptool$exe';
}
assert(false, 'Invalid artifact $artifact.');
return null;
@@ -543,6 +571,11 @@
.childDirectory(_enginePlatformDirectoryName(platform))
.childFile(_artifactToFileName(artifact, platform, mode))
.path;
+ case Artifact.uwptool:
+ return _cache.getArtifactDirectory('engine')
+ .childDirectory('windows-uwp-x64-${getNameForBuildMode(mode ?? BuildMode.debug)}')
+ .childFile(_artifactToFileName(artifact, platform, mode))
+ .path;
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
@@ -824,6 +857,8 @@
case Artifact.frontendServerSnapshotForEngineDartSdk:
return _fileSystem.path.join(_hostEngineOutPath, 'gen', artifactFileName);
break;
+ case Artifact.uwptool:
+ return _fileSystem.path.join(_hostEngineOutPath, artifactFileName);
}
assert(false, 'Invalid artifact $artifact.');
return null;
diff --git a/packages/flutter_tools/lib/src/cmake.dart b/packages/flutter_tools/lib/src/cmake.dart
index 7dd735d..9ad626e 100644
--- a/packages/flutter_tools/lib/src/cmake.dart
+++ b/packages/flutter_tools/lib/src/cmake.dart
@@ -4,6 +4,7 @@
// @dart = 2.8
+import 'base/file_system.dart';
import 'project.dart';
/// Extracts the `BINARY_NAME` from a project's CMake file.
@@ -23,6 +24,23 @@
return null;
}
+/// Extracts the `PACKAGE_GUID` from a project's CMake file.
+///
+/// Returns `null` if it cannot be found.
+String getCmakePackageGuid(File cmakeFile) {
+ if (!cmakeFile.existsSync()) {
+ return null;
+ }
+ final RegExp nameSetPattern = RegExp(r'^\s*set\(PACKAGE_GUID\s*"(.*)"\s*\)\s*$');
+ for (final String line in cmakeFile.readAsLinesSync()) {
+ final RegExpMatch match = nameSetPattern.firstMatch(line);
+ if (match != null) {
+ return match.group(1);
+ }
+ }
+ return null;
+}
+
String _escapeBackslashes(String s) {
return s.replaceAll(r'\', r'\\');
}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 4c38bfc..8769d1f 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -64,6 +64,7 @@
import 'runner/local_engine.dart';
import 'version.dart';
import 'web/workflow.dart';
+import 'windows/uwptool.dart';
import 'windows/visual_studio.dart';
import 'windows/visual_studio_validator.dart';
import 'windows/windows_workflow.dart';
@@ -206,6 +207,11 @@
logger: globals.logger,
platform: globals.platform
),
+ uwptool: UwpTool(
+ artifacts: globals.artifacts,
+ logger: globals.logger,
+ processManager: globals.processManager,
+ ),
),
DevtoolsLauncher: () => DevtoolsServerLauncher(
processManager: globals.processManager,
diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart
index 65beb69..11b3f92 100644
--- a/packages/flutter_tools/lib/src/flutter_application_package.dart
+++ b/packages/flutter_tools/lib/src/flutter_application_package.dart
@@ -108,9 +108,7 @@
? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia)
: FuchsiaApp.fromPrebuiltApp(applicationBinary);
case TargetPlatform.windows_uwp_x64:
- return applicationBinary == null
- ? WindowsApp.fromWindowsProject(FlutterProject.current().windowsUwp)
- : WindowsApp.fromPrebuiltApp(applicationBinary);
+ return BuildableUwpApp(project: FlutterProject.current().windowsUwp);
}
assert(platform != null);
return null;
diff --git a/packages/flutter_tools/lib/src/flutter_device_manager.dart b/packages/flutter_tools/lib/src/flutter_device_manager.dart
index 08b005b..10c3158 100644
--- a/packages/flutter_tools/lib/src/flutter_device_manager.dart
+++ b/packages/flutter_tools/lib/src/flutter_device_manager.dart
@@ -35,6 +35,7 @@
import 'tester/flutter_tester.dart';
import 'version.dart';
import 'web/web_device.dart';
+import 'windows/uwptool.dart';
import 'windows/windows_device.dart';
import 'windows/windows_workflow.dart';
@@ -61,6 +62,7 @@
@required WindowsWorkflow windowsWorkflow,
@required Terminal terminal,
@required CustomDevicesConfig customDevicesConfig,
+ @required UwpTool uwptool,
}) : deviceDiscoverers = <DeviceDiscovery>[
AndroidDevices(
logger: logger,
@@ -118,6 +120,7 @@
fileSystem: fileSystem,
windowsWorkflow: windowsWorkflow,
featureFlags: featureFlags,
+ uwptool: uwptool,
),
WebDevices(
featureFlags: featureFlags,
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 0dea5ca..d72f5d7 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -16,6 +16,7 @@
import 'base/logger.dart';
import 'build_info.dart';
import 'bundle.dart' as bundle;
+import 'cmake.dart';
import 'features.dart';
import 'flutter_manifest.dart';
import 'flutter_plugins.dart';
@@ -1221,8 +1222,38 @@
@override
String get _childDirectory => 'winuwp';
+ File get runnerCmakeFile => _editableDirectory.childDirectory('runner_uwp').childFile('CMakeLists.txt');
+
/// Eventually this will be used to check if the user's unstable project needs to be regenerated.
int get projectVersion => int.tryParse(_editableDirectory.childFile('project_version').readAsStringSync());
+
+ /// Retrieve the GUID of the UWP package.
+ String get packageGuid => _packageGuid ??= getCmakePackageGuid(runnerCmakeFile);
+ String _packageGuid;
+
+ File get appManifest => _editableDirectory.childDirectory('runner_uwp').childFile('appxmanifest.in');
+
+ String get packageVersion => _packageVersion ??= parseAppVersion(this);
+ String _packageVersion;
+}
+
+@visibleForTesting
+String parseAppVersion(WindowsUwpProject project) {
+ final File appManifestFile = project.appManifest;
+ if (!appManifestFile.existsSync()) {
+ return null;
+ }
+
+ XmlDocument document;
+ try {
+ document = XmlDocument.parse(appManifestFile.readAsStringSync());
+ } on XmlParserException {
+ throwToolExit('Error parsing $appManifestFile. Please ensure that the appx manifest is a valid XML document and try again.');
+ }
+ for (final XmlElement metaData in document.findAllElements('Identity')) {
+ return metaData.getAttribute('Version');
+ }
+ return null;
}
/// The Linux sub project.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index fb8f43d..ff8bf1f 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -1466,7 +1466,9 @@
case TargetPlatform.fuchsia_x64:
case TargetPlatform.tester:
case TargetPlatform.windows_uwp_x64:
- // No artifacts currently supported.
+ if (featureFlags.isWindowsUwpEnabled) {
+ return DevelopmentArtifact.windowsUwp;
+ }
return null;
}
return null;
diff --git a/packages/flutter_tools/lib/src/windows/application_package.dart b/packages/flutter_tools/lib/src/windows/application_package.dart
index f0961b0..de5e212 100644
--- a/packages/flutter_tools/lib/src/windows/application_package.dart
+++ b/packages/flutter_tools/lib/src/windows/application_package.dart
@@ -75,3 +75,14 @@
@override
String get name => project.parent.manifest.appName;
}
+
+class BuildableUwpApp extends ApplicationPackage {
+ BuildableUwpApp({@required this.project}) : super(id: project.packageGuid);
+
+ final WindowsUwpProject project;
+
+ String get projectVersion => project.packageVersion;
+
+ @override
+ String get name => getCmakeExecutableName(project);
+}
diff --git a/packages/flutter_tools/lib/src/windows/uwptool.dart b/packages/flutter_tools/lib/src/windows/uwptool.dart
new file mode 100644
index 0000000..e752236
--- /dev/null
+++ b/packages/flutter_tools/lib/src/windows/uwptool.dart
@@ -0,0 +1,87 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:process/process.dart';
+
+import '../artifacts.dart';
+import '../base/logger.dart';
+import '../base/process.dart';
+
+/// The uwptool command-line tool.
+///
+/// `uwptool` is a host utility command-line tool that supports a variety of
+/// actions related to Universal Windows Platform (UWP) applications, including
+/// installing and uninstalling apps, querying installed apps, and launching
+/// apps.
+class UwpTool {
+ UwpTool({
+ @required Artifacts artifacts,
+ @required Logger logger,
+ @required ProcessManager processManager,
+ }) : _artifacts = artifacts,
+ _logger = logger,
+ _processUtils = ProcessUtils(processManager: processManager, logger: logger);
+
+ final Artifacts _artifacts;
+ final Logger _logger;
+ final ProcessUtils _processUtils;
+
+ String get _binaryPath => _artifacts.getArtifactPath(Artifact.uwptool);
+
+ Future<List<String>> listApps() async {
+ final List<String> launchCommand = <String>[
+ _binaryPath,
+ 'listapps',
+ ];
+ final RunResult result = await _processUtils.run(launchCommand);
+ if (result.exitCode != 0) {
+ _logger.printError('Failed to list installed UWP apps: ${result.stderr}');
+ return <String>[];
+ }
+ final List<String> appIds = <String>[];
+ for (final String line in result.stdout.toString().split('\n')) {
+ final String appId = line.trim();
+ if (appId.isNotEmpty) {
+ appIds.add(appId);
+ }
+ }
+ return appIds;
+ }
+
+ /// Returns the app ID for the specified package ID.
+ ///
+ /// 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;
+ }
+ }
+ return null;
+ }
+
+ /// Launches the app with the specified app ID.
+ ///
+ /// On success, returns the process ID of the launched app, otherwise null.
+ Future<int/*?*/> launchApp(String appId, List<String> args) async {
+ final List<String> launchCommand = <String>[
+ _binaryPath,
+ 'launch',
+ appId
+ ] + args;
+ final RunResult result = await _processUtils.run(launchCommand);
+ if (result.exitCode != 0) {
+ _logger.printError('Failed to launch app $appId: ${result.stderr}');
+ return null;
+ }
+ // Read the process ID from stdout.
+ return int.tryParse(result.stdout.toString().trim());
+ }
+}
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart
index aa4a3b1..b657739 100644
--- a/packages/flutter_tools/lib/src/windows/windows_device.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -4,20 +4,26 @@
// @dart = 2.8
+import 'dart:async';
+
import 'package:meta/meta.dart';
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';
import '../build_info.dart';
import '../desktop_device.dart';
import '../device.dart';
+import '../device_port_forwarder.dart';
import '../features.dart';
import '../project.dart';
import 'application_package.dart';
import 'build_windows.dart';
+import 'uwptool.dart';
import 'windows_workflow.dart';
/// A device that represents a desktop Windows target.
@@ -71,21 +77,33 @@
}
// A device that represents a desktop Windows UWP target.
-class WindowsUWPDevice extends DesktopDevice {
+class WindowsUWPDevice extends Device {
WindowsUWPDevice({
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
- }) : super(
- 'winuwp',
- platformType: PlatformType.windows,
- ephemeral: false,
- processManager: processManager,
- logger: logger,
- fileSystem: fileSystem,
- operatingSystemUtils: operatingSystemUtils,
- );
+ @required UwpTool uwptool,
+ }) : _logger = logger,
+ _processManager = processManager,
+ _operatingSystemUtils = operatingSystemUtils,
+ _fileSystem = fileSystem,
+ _uwptool = uwptool,
+ super(
+ 'winuwp',
+ platformType: PlatformType.windows,
+ ephemeral: false,
+ category: Category.desktop,
+ );
+
+ final ProcessManager _processManager;
+ final Logger _logger;
+ final FileSystem _fileSystem;
+ final OperatingSystemUtils _operatingSystemUtils;
+ final UwpTool _uwptool;
+ BuildMode _buildMode;
+
+ int _processId;
@override
bool isSupported() => true;
@@ -98,29 +116,150 @@
@override
bool isSupportedForProject(FlutterProject flutterProject) {
- // TODO(flutter): update with detection once FlutterProject knows
- // about the UWP structure.
- return true;
+ return flutterProject.windowsUwp.existsSync();
}
@override
- Future<void> buildForDevice(
- covariant WindowsApp package, {
+ void clearLogs() { }
+
+ @override
+ Future<void> dispose() async { }
+
+ @override
+ Future<String> get emulatorId => null;
+
+ @override
+ FutureOr<DeviceLogReader> getLogReader({covariant BuildableUwpApp app, bool includePastLogs = false}) {
+ return NoOpDeviceLogReader('winuwp');
+ }
+
+ @override
+ Future<bool> installApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+ /// The cmake build generates an install powershell script.
+ /// build\winuwp\runner_uwp\AppPackages\<app-name>\<app-name>_<app-version>_<cmake-config>\Add-AppDevPackage.ps1
+ final String binaryName = app.name;
+ final String packageVersion = app.projectVersion;
+ if (packageVersion == null) {
+ return false;
+ }
+ 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;
+ }
+
+ @override
+ Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async => false;
+
+ @override
+ Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app) async => false;
+
+ @override
+ Future<bool> get isLocalEmulator async => false;
+
+ @override
+ DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
+
+ @override
+ Future<String> get sdkNameAndVersion async => '';
+
+ @override
+ Future<LaunchResult> startApp(covariant BuildableUwpApp package, {
String mainPath,
- BuildInfo buildInfo,
+ String route,
+ DebuggingOptions debuggingOptions,
+ Map<String, dynamic> platformArgs,
+ bool prebuiltApplication = false,
+ bool ipv6 = false,
+ String userIdentifier,
}) async {
- await buildWindowsUwp(
- FlutterProject.current().windowsUwp,
- buildInfo,
- target: mainPath,
- );
+ _buildMode = debuggingOptions.buildInfo.mode;
+ if (!prebuiltApplication) {
+ await buildWindowsUwp(
+ package.project,
+ debuggingOptions.buildInfo,
+ target: mainPath,
+ );
+ }
+ if (!await isAppInstalled(package) && !await installApp(package)) {
+ _logger.printError('Failed to install app package');
+ return LaunchResult.failed();
+ }
+
+ final String guid = package.id;
+ if (guid == null) {
+ _logger.printError('Could not find PACKAGE_GUID in ${package.project.runnerCmakeFile.path}');
+ return LaunchResult.failed();
+ }
+
+ final String appId = await _uwptool.getAppIdFromPackageId(guid);
+
+ if (debuggingOptions.buildInfo.mode.isRelease) {
+ _processId = await _uwptool.launchApp(appId, <String>[]);
+ return _processId != null ? LaunchResult.succeeded() : LaunchResult.failed();
+ }
+
+ /// If the terminal is attached, prompt the user to open the firewall port.
+ 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'
+ 'Press "Y/y" once this is complete.'
+ );
+ }
+
+ /// Currently we do not have a way to discover the VM Service URI.
+ final int port = debuggingOptions.deviceVmServicePort ?? await _operatingSystemUtils.findFreePort();
+ final List<String> args = <String>[
+ '--observatory-port=$port',
+ '--disable-service-auth-codes',
+ '--enable-dart-profiling',
+ if (debuggingOptions.startPaused) '--start-paused',
+ if (debuggingOptions.useTestFonts) '--use-test-fonts',
+ if (debuggingOptions.debuggingEnabled) ...<String>[
+ '--enable-checked-mode',
+ '--verify-entry-points',
+ ],
+ if (debuggingOptions.enableSoftwareRendering) '--enable-software-rendering',
+ if (debuggingOptions.skiaDeterministicRendering) '--skia-deterministic-rendering',
+ if (debuggingOptions.traceSkia) '--trace-skia',
+ if (debuggingOptions.traceAllowlist != null) '--trace-allowlist="${debuggingOptions.traceAllowlist}"',
+ if (debuggingOptions.endlessTraceBuffer) '--endless-trace-buffer',
+ if (debuggingOptions.dumpSkpOnShaderCompilation) '--dump-skp-on-shader-compilation',
+ if (debuggingOptions.verboseSystemLogs) '--verbose-logging',
+ if (debuggingOptions.cacheSkSL) '--cache-sksl',
+ if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache',
+ if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup',
+ ];
+ _processId = await _uwptool.launchApp(appId, args);
+ if (_processId == null) {
+ return LaunchResult.failed();
+ }
+ return LaunchResult.succeeded(observatoryUri: Uri.parse('http://localhost:$port'));
}
@override
- String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
- // TODO(flutter): update once application package factory knows about UWP bundle.
- throw UnsupportedError('Windows UWP device not implemented.');
+ Future<bool> stopApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+ if (_processId != null) {
+ return _processManager.killPid(_processId);
+ }
+ return false;
}
+
+ @override
+ Future<bool> uninstallApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
+ return false;
+ }
+
+ @override
+ FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
}
class WindowsDevices extends PollingDeviceDiscovery {
@@ -131,12 +270,14 @@
@required OperatingSystemUtils operatingSystemUtils,
@required WindowsWorkflow windowsWorkflow,
@required FeatureFlags featureFlags,
+ @required UwpTool uwptool,
}) : _fileSystem = fileSystem,
_logger = logger,
_processManager = processManager,
_operatingSystemUtils = operatingSystemUtils,
_windowsWorkflow = windowsWorkflow,
_featureFlags = featureFlags,
+ _uwptool = uwptool,
super('windows devices');
final FileSystem _fileSystem;
@@ -145,6 +286,7 @@
final OperatingSystemUtils _operatingSystemUtils;
final WindowsWorkflow _windowsWorkflow;
final FeatureFlags _featureFlags;
+ final UwpTool _uwptool;
@override
bool get supportsPlatform => _windowsWorkflow.appliesToHostPlatform;
@@ -170,6 +312,7 @@
logger: _logger,
processManager: _processManager,
operatingSystemUtils: _operatingSystemUtils,
+ uwptool: _uwptool,
)
];
}
diff --git a/packages/flutter_tools/test/general.shard/windows/project_test.dart b/packages/flutter_tools/test/general.shard/windows/project_test.dart
new file mode 100644
index 0000000..9b907c4
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/windows/project_test.dart
@@ -0,0 +1,140 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/project.dart';
+
+import '../../src/common.dart';
+
+const String kExampleManifest = r'''
+<?xml version="1.0" encoding="utf-8"?>
+<Package
+ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+ xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+ IgnorableNamespaces="uap mp">
+
+ <Identity Name="@PACKAGE_GUID@" Publisher="CN=CMake Test Cert" Version="2.3.1.4" />
+ <mp:PhoneIdentity PhoneProductId="@PACKAGE_GUID@" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+ <Properties>
+ <DisplayName>@SHORT_NAME@</DisplayName>
+ <PublisherDisplayName>CMake Test Cert</PublisherDisplayName>
+ <Logo>Assets/StoreLogo.png</Logo>
+ </Properties>
+
+ <Dependencies>
+ <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.65535.65535" />
+ </Dependencies>
+
+ <Resources>
+ <Resource Language="x-generate" />
+ </Resources>
+ <Applications>
+ <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="@SHORT_NAME@.App">
+ <uap:VisualElements
+ DisplayName="@SHORT_NAME@"
+ Description="@SHORT_NAME@"
+ BackgroundColor="#336699"
+ Square150x150Logo="Assets/Square150x150Logo.png"
+ Square44x44Logo="Assets/Square44x44Logo.png"
+ >
+ <uap:SplashScreen Image="Assets/SplashScreen.png" />
+ </uap:VisualElements>
+ </Application>
+ </Applications>
+ <Capabilities>
+ <Capability Name="internetClientServer"/>
+ <Capability Name="internetClient"/>
+ <Capability Name="privateNetworkClientServer"/>
+ <Capability Name="codeGeneration"/></Capabilities>
+</Package>
+''';
+
+void main() {
+ testWithoutContext('Project can parse the app version from the appx manifest', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ fileSystem.file('winuwp/runner_uwp/appxmanifest.in')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kExampleManifest);
+
+ final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+ expect(flutterProject.windowsUwp.packageVersion, '2.3.1.4');
+ });
+
+ testWithoutContext('Project returns null if appx manifest does not exist', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+
+ final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+ expect(flutterProject.windowsUwp.packageVersion, null);
+ });
+
+ testWithoutContext('Project throws a tool exit if appxmanifest is not valid xml', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ fileSystem.file('winuwp/runner_uwp/appxmanifest.in')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('[');
+
+ final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+ expect(() => flutterProject.windowsUwp.packageVersion, throwsToolExit());
+ });
+
+ testWithoutContext('Can parse the PACKAGE_GUID from the Cmake manifest', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ fileSystem.file('winuwp/runner_uwp/CMakeLists.txt')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(r'''
+cmake_minimum_required (VERSION 3.8)
+set(CMAKE_SYSTEM_NAME WindowsStore)
+set(CMAKE_SYSTEM_VERSION 10.0)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+include(CMakePrintHelpers)
+
+project ("TestBedUWP")
+
+set(APP_MANIFEST_NAME Package.appxmanifest)
+set(APP_MANIFEST_TARGET_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${APP_MANIFEST_NAME})
+set(SHORT_NAME ${BINARY_NAME})
+set(PACKAGE_GUID "F941A77F-8AE1-4E3E-9611-68FBD3C62AE8")
+
+''');
+
+ final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+ expect(flutterProject.windowsUwp.packageGuid, 'F941A77F-8AE1-4E3E-9611-68FBD3C62AE8');
+ });
+
+ testWithoutContext('Returns null if the PACKAGE_GUID cannot be found in the Cmake file', () {
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ fileSystem.file('winuwp/runner_uwp/CMakeLists.txt')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(r'''
+cmake_minimum_required (VERSION 3.8)
+set(CMAKE_SYSTEM_NAME WindowsStore)
+set(CMAKE_SYSTEM_VERSION 10.0)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+include(CMakePrintHelpers)
+
+project ("TestBedUWP")
+
+set(APP_MANIFEST_NAME Package.appxmanifest)
+set(APP_MANIFEST_TARGET_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${APP_MANIFEST_NAME})
+set(SHORT_NAME ${BINARY_NAME})
+''');
+
+ final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+
+ expect(flutterProject.windowsUwp.packageGuid, null);
+ });
+}
diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
index f145b5e..41637a8 100644
--- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
@@ -9,10 +9,12 @@
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/windows/application_package.dart';
+import 'package:flutter_tools/src/windows/uwptool.dart';
import 'package:flutter_tools/src/windows/windows_device.dart';
import 'package:flutter_tools/src/windows/windows_workflow.dart';
import 'package:test/fake.dart';
@@ -42,14 +44,14 @@
testWithoutContext('WindowsUwpDevice defaults', () async {
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice();
- final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
+ final FakeBuildableUwpApp package = FakeBuildableUwpApp();
expect(await windowsDevice.targetPlatform, TargetPlatform.windows_uwp_x64);
expect(windowsDevice.name, 'Windows (UWP)');
- expect(await windowsDevice.installApp(windowsApp), true);
- expect(await windowsDevice.uninstallApp(windowsApp), true);
- expect(await windowsDevice.isLatestBuildInstalled(windowsApp), true);
- expect(await windowsDevice.isAppInstalled(windowsApp), true);
+ expect(await windowsDevice.installApp(package), true);
+ expect(await windowsDevice.uninstallApp(package), false);
+ expect(await windowsDevice.isLatestBuildInstalled(package), false);
+ expect(await windowsDevice.isAppInstalled(package), false);
expect(windowsDevice.category, Category.desktop);
expect(windowsDevice.supportsRuntimeMode(BuildMode.debug), true);
@@ -69,6 +71,7 @@
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
+ uwptool: FakeUwpTool(),
).devices, <Device>[]);
});
@@ -83,6 +86,7 @@
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
+ uwptool: FakeUwpTool(),
).devices, hasLength(1));
});
@@ -98,6 +102,7 @@
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
featureFlags: featureFlags,
+ uwptool: FakeUwpTool(),
).devices, hasLength(2));
});
@@ -112,6 +117,7 @@
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
+ uwptool: FakeUwpTool(),
);
// Timeout ignored.
final List<Device> devices = await windowsDevices.discoverDevices(timeout: const Duration(seconds: 10));
@@ -159,6 +165,70 @@
expect(windowsDevice.executablePathForDevice(fakeApp, BuildMode.profile), 'profile/executable');
expect(windowsDevice.executablePathForDevice(fakeApp, BuildMode.release), 'release/executable');
});
+
+ testWithoutContext('WinUWPDevice can launch application', () async {
+ 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();
+
+ final LaunchResult result = await windowsDevice.startApp(
+ package,
+ debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+ prebuiltApplication: true,
+ platformArgs: <String, Object>{},
+ );
+
+ expect(result.started, true);
+ expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
+ expect(uwptool.launchRequests.single.args, <String>[
+ '--observatory-port=12345',
+ '--disable-service-auth-codes',
+ '--enable-dart-profiling',
+ '--enable-checked-mode',
+ '--verify-entry-points',
+ ]);
+ });
+
+ testWithoutContext('WinUWPDevice can launch application in release mode', () async {
+ 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();
+
+ final LaunchResult result = await windowsDevice.startApp(
+ package,
+ debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
+ prebuiltApplication: true,
+ platformArgs: <String, Object>{},
+ );
+
+ expect(result.started, true);
+ expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
+ expect(uwptool.launchRequests.single.args, <String>[]);
+ });
}
FlutterProject setUpFlutterProject(Directory directory) {
@@ -186,12 +256,14 @@
FileSystem fileSystem,
Logger logger,
ProcessManager processManager,
+ UwpTool uwptool,
}) {
return WindowsUWPDevice(
fileSystem: fileSystem ?? MemoryFileSystem.test(),
logger: logger ?? BufferLogger.test(),
processManager: processManager ?? FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
+ uwptool: uwptool ?? FakeUwpTool(),
);
}
@@ -199,3 +271,52 @@
@override
String executable(BuildMode buildMode) => '${buildMode.name}/executable';
}
+
+class FakeBuildableUwpApp extends Fake implements BuildableUwpApp {
+ @override
+ String get id => 'PACKAGE-ID';
+ @override
+ String get name => 'testapp';
+ @override
+ String get projectVersion => '1.2.3.4';
+}
+
+class FakeUwpTool implements UwpTool {
+ final List<_LaunchRequest> launchRequests = <_LaunchRequest>[];
+ final List<_LookupAppIdRequest> lookupAppIdRequests = <_LookupAppIdRequest>[];
+
+ @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',
+ ];
+ }
+
+ @override
+ Future<String> getAppIdFromPackageId(String packageId) async {
+ lookupAppIdRequests.add(_LookupAppIdRequest(packageId));
+ return '${packageId}_asdfghjkl';
+ }
+
+ @override
+ Future<int> launchApp(String appId, List<String> args) async {
+ launchRequests.add(_LaunchRequest(appId, args));
+ return 42;
+ }
+}
+
+class _LookupAppIdRequest {
+ const _LookupAppIdRequest(this.packageId);
+
+ final String packageId;
+}
+
+class _LaunchRequest {
+ const _LaunchRequest(this.appId, this.args);
+
+ final String appId;
+ final List<String> args;
+}