Add pre-stable support for create on Windows (#51895)
Adds initial support for flutter create of apps and plugins. This is derived from the current FDE example app and sample plugin, adding template values where relevant.
Since the APIs/tooling/template aren't stable yet, the app template includes a version marker, which will be updated each time there's a breaking change. The build now checks that the template version matches the version known by that version of the tool, and gives a specific error message when there's a mismatch, which improves over the current breaking change experience of hitting whatever build failure the breaking change causes and having to figure out that the problem is that the runner is out of date. It also adds a warning to the create output about the fact that it won't be stable.
Plugins don't currently have a version marker since in practice this is not a significant problem for plugins yet the way it is for runners; we can add it later if that changes.
Fixes #30704
diff --git a/.gitattributes b/.gitattributes
index 31fda4b..1f9dafd 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,8 +15,17 @@
*.yaml text
# Make sure that these Windows files always have CRLF line endings in checkout
-*.bat text eol=crlf
-*.ps1 text eol=crlf
+*.bat text eol=crlf
+*.ps1 text eol=crlf
+*.rc text eol=crlf
+*.sln text eol=crlf
+*.props text eol=crlf
+*.vcxproj text eol=crlf
+*.vcxproj.filters text eol=crlf
+# Including templatized versions.
+*.sln.tmpl text eol=crlf
+*.props.tmpl text eol=crlf
+*.vcxproj.tmpl text eol=crlf
# Never perform LF normalization on these files
*.ico binary
diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart
index 20f65e9..7a852d9 100644
--- a/dev/bots/analyze.dart
+++ b/dev/bots/analyze.dart
@@ -603,6 +603,7 @@
.where((File file) => path.extension(file.path) != '.snapshot')
.where((File file) => path.extension(file.path) != '.png')
.where((File file) => path.extension(file.path) != '.jpg')
+ .where((File file) => path.extension(file.path) != '.ico')
.where((File file) => path.extension(file.path) != '.jar')
.toList();
final List<String> problems = <String>[];
@@ -685,7 +686,9 @@
// DO NOT ADD ANY ENTRIES TO THIS LIST.
// We have a policy of not checking in binaries into this repository.
-// If you have binaries to add, please consult Hixie for advice.
+// If you are adding/changing template images, use the flutter_template_images
+// package and a .img.tmpl placeholder instead.
+// If you have other binaries to add, please consult Hixie for advice.
final Set<Hash256> _grandfatheredBinaries = <Hash256>{
// DEFAULT ICON IMAGES
@@ -1044,7 +1047,9 @@
Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256> grandfatheredBinaries }) async {
// Please do not add anything to the _grandfatheredBinaries set above.
// We have a policy of not checking in binaries into this repository.
- // If you have binaries to add, please consult Hixie for advice.
+ // If you are adding/changing template images, use the flutter_template_images
+ // package and a .img.tmpl placeholder instead.
+ // If you have other binaries to add, please consult Hixie for advice.
assert(
_grandfatheredBinaries
.expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d])
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index e0e28ed..d8db4e0 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -148,7 +148,7 @@
final String assetBasePath = globals.fs.path.dirname(globals.fs.path.absolute(manifestPath));
- final PackageMap packageMap = PackageMap(packagesPath);
+ final PackageMap packageMap = PackageMap(packagesPath, fileSystem: globals.fs);
final List<Uri> wildcardDirectories = <Uri>[];
// The _assetVariants map contains an entry for each asset listed
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index c5ad2c3..81e117a 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -407,6 +407,7 @@
web: featureFlags.isWebEnabled,
linux: featureFlags.isLinuxEnabled,
macos: featureFlags.isMacOSEnabled,
+ windows: featureFlags.isWindowsEnabled,
);
final String relativeDirPath = globals.fs.path.relative(projectDirPath);
@@ -507,6 +508,12 @@
'You will likely need to re-create the "linux" directory after future '
'Flutter updates.');
}
+ if (featureFlags.isWindowsEnabled) {
+ globals.printStatus('');
+ globals.printStatus('WARNING: The Windows tooling and APIs are not yet stable. '
+ 'You will likely need to re-create the "windows" directory after future '
+ 'Flutter updates.');
+ }
}
return FlutterCommandResult.success();
}
@@ -517,7 +524,7 @@
? stringArg('description')
: 'A new flutter module project.';
templateContext['description'] = description;
- generatedCount += _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
+ generatedCount += await _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.create,
@@ -536,7 +543,7 @@
? stringArg('description')
: 'A new Flutter package project.';
templateContext['description'] = description;
- generatedCount += _renderTemplate('package', directory, templateContext, overwrite: overwrite);
+ generatedCount += await _renderTemplate('package', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.createPackage,
@@ -553,7 +560,7 @@
? stringArg('description')
: 'A new flutter plugin project.';
templateContext['description'] = description;
- generatedCount += _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
+ generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.createPlugin,
@@ -581,13 +588,13 @@
Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async {
int generatedCount = 0;
- generatedCount += _renderTemplate('app', directory, templateContext, overwrite: overwrite);
+ generatedCount += await _renderTemplate('app', directory, templateContext, overwrite: overwrite);
final FlutterProject project = FlutterProject.fromDirectory(directory);
generatedCount += _injectGradleWrapper(project);
if (boolArg('with-driver-test')) {
final Directory testDirectory = directory.childDirectory('test_driver');
- generatedCount += _renderTemplate('driver', testDirectory, templateContext, overwrite: overwrite);
+ generatedCount += await _renderTemplate('driver', testDirectory, templateContext, overwrite: overwrite);
}
if (boolArg('pub')) {
@@ -626,6 +633,7 @@
bool web = false,
bool linux = false,
bool macos = false,
+ bool windows = false,
}) {
flutterRoot = globals.fs.path.normalize(flutterRoot);
@@ -651,6 +659,7 @@
'pluginClass': pluginClass,
'pluginDartClass': pluginDartClass,
'pluginCppHeaderGuard': projectName.toUpperCase(),
+ 'pluginProjectUUID': Uuid().generateV4().toUpperCase(),
'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage,
'iosLanguage': iosLanguage,
@@ -659,12 +668,13 @@
'web': web,
'linux': linux,
'macos': macos,
+ 'windows': windows,
'year': DateTime.now().year,
};
}
- int _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) {
- final Template template = Template.fromName(templateName);
+ Future<int> _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) async {
+ final Template template = await Template.fromName(templateName, fileSystem: globals.fs);
return template.render(directory, context, overwriteExisting: overwrite);
}
diff --git a/packages/flutter_tools/lib/src/commands/ide_config.dart b/packages/flutter_tools/lib/src/commands/ide_config.dart
index 91b78b8..ae341c7 100644
--- a/packages/flutter_tools/lib/src/commands/ide_config.dart
+++ b/packages/flutter_tools/lib/src/commands/ide_config.dart
@@ -247,7 +247,7 @@
}
int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
- final Template template = Template(_templateDirectory, _templateDirectory);
+ final Template template = Template(_templateDirectory, _templateDirectory, null, fileSystem: globals.fs);
return template.render(
globals.fs.directory(dirPath),
context,
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index a845bc5..2694ab0 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -196,7 +196,7 @@
/// Converts filesystem paths to package URIs.
class PackageUriMapper {
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
- final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath)).map;
+ final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath), fileSystem: globals.fs).map;
final bool isWindowsPath = globals.platform.isWindows && !scriptPath.startsWith('org-dartlang-app');
final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString();
for (final String packageName in packageMap.keys) {
diff --git a/packages/flutter_tools/lib/src/dart/package_map.dart b/packages/flutter_tools/lib/src/dart/package_map.dart
index ddb072c..316aae4 100644
--- a/packages/flutter_tools/lib/src/dart/package_map.dart
+++ b/packages/flutter_tools/lib/src/dart/package_map.dart
@@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:meta/meta.dart';
// TODO(bkonyi): remove deprecated member usage, https://github.com/flutter/flutter/issues/51951
// ignore: deprecated_member_use
import 'package:package_config/packages_file.dart' as packages_file;
-import '../globals.dart' as globals;
+import '../base/file_system.dart';
+import '../globals.dart' as globals hide fs;
const String kPackagesFileName = '.packages';
-Map<String, Uri> _parse(String packagesPath) {
- final List<int> source = globals.fs.file(packagesPath).readAsBytesSync();
+Map<String, Uri> _parse(String packagesPath, FileSystem fileSystem) {
+ final List<int> source = fileSystem.file(packagesPath).readAsBytesSync();
return packages_file.parse(source,
Uri.file(packagesPath, windows: globals.platform.isWindows));
}
class PackageMap {
- PackageMap(this.packagesPath);
+ PackageMap(this.packagesPath, {
+ @required FileSystem fileSystem,
+ }) : _fileSystem = fileSystem;
/// Create a [PackageMap] for testing.
- PackageMap.test(Map<String, Uri> input)
- : packagesPath = '.packages',
- _map = input;
+ PackageMap.test(Map<String, Uri> input, {
+ @required FileSystem fileSystem,
+ }) : packagesPath = '.packages',
+ _map = input,
+ _fileSystem = fileSystem;
+
+ final FileSystem _fileSystem;
static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;
@@ -38,7 +46,7 @@
/// Load and parses the .packages file.
void load() {
- _map ??= _parse(packagesPath);
+ _map ??= _parse(packagesPath, _fileSystem);
}
Map<String, Uri> get map {
@@ -59,17 +67,17 @@
if (packageBase == null) {
return null;
}
- final String packageRelativePath = globals.fs.path.joinAll(pathSegments);
- return packageBase.resolveUri(globals.fs.path.toUri(packageRelativePath));
+ final String packageRelativePath = _fileSystem.path.joinAll(pathSegments);
+ return packageBase.resolveUri(_fileSystem.path.toUri(packageRelativePath));
}
String checkValid() {
- if (globals.fs.isFileSync(packagesPath)) {
+ if (_fileSystem.isFileSync(packagesPath)) {
return null;
}
String message = '$packagesPath does not exist.';
- final String pubspecPath = globals.fs.path.absolute(globals.fs.path.dirname(packagesPath), 'pubspec.yaml');
- if (globals.fs.isFileSync(pubspecPath)) {
+ final String pubspecPath = _fileSystem.path.absolute(_fileSystem.path.dirname(packagesPath), 'pubspec.yaml');
+ if (_fileSystem.isFileSync(pubspecPath)) {
message += '\nDid you run "flutter pub get" in this directory?';
} else {
message += '\nDid you run this command from the same directory as your pubspec.yaml file?';
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 2b1d41d..b73b42d 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -307,7 +307,7 @@
project.directory.path,
PackageMap.globalPackagesPath,
);
- packages = PackageMap(packagesFile).map;
+ packages = PackageMap(packagesFile, fileSystem: globals.fs).map;
} on FormatException catch (e) {
globals.printTrace('Invalid .packages file: $e');
return plugins;
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 9bd80fa..2926a18 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -460,7 +460,7 @@
Map<String, String> _buildSettings;
Future<void> ensureReadyForPlatformSpecificTooling() async {
- _regenerateFromTemplateIfNeeded();
+ await _regenerateFromTemplateIfNeeded();
if (!_flutterLibRoot.existsSync()) {
return;
}
@@ -477,7 +477,7 @@
}
}
- void _regenerateFromTemplateIfNeeded() {
+ Future<void> _regenerateFromTemplateIfNeeded() async {
if (!isModule) {
return;
}
@@ -491,18 +491,18 @@
}
_deleteIfExistsSync(ephemeralDirectory);
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
// Add ephemeral host app, if a editable host app does not already exist.
if (!_editableDirectory.existsSync()) {
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
ephemeralDirectory,
);
if (hasPlugins(parent)) {
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
ephemeralDirectory,
);
@@ -542,19 +542,19 @@
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
}
_deleteIfExistsSync(ephemeralDirectory);
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
_editableDirectory,
);
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
_editableDirectory,
);
- _overwriteFromTemplate(
+ await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
_editableDirectory,
);
@@ -579,8 +579,8 @@
: hostAppRoot.childDirectory(_hostAppBundleName);
}
- void _overwriteFromTemplate(String path, Directory target) {
- final Template template = Template.fromName(path);
+ Future<void> _overwriteFromTemplate(String path, Directory target) async {
+ final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render(
target,
<String, dynamic>{
@@ -679,11 +679,11 @@
Future<void> ensureReadyForPlatformSpecificTooling() async {
if (isModule && _shouldRegenerateFromTemplate()) {
- _regenerateLibrary();
+ await _regenerateLibrary();
// Add ephemeral host app, if an editable host app does not already exist.
if (!_editableHostAppDirectory.existsSync()) {
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
}
}
if (!hostAppGradleRoot.existsSync()) {
@@ -704,10 +704,10 @@
if (_editableHostAppDirectory.existsSync()) {
throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
}
- _regenerateLibrary();
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
+ await _regenerateLibrary();
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
await injectPlugins(parent);
@@ -717,19 +717,19 @@
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
- void _regenerateLibrary() {
+ Future<void> _regenerateLibrary() async {
_deleteIfExistsSync(ephemeralDirectory);
- _overwriteFromTemplate(globals.fs.path.join(
+ await _overwriteFromTemplate(globals.fs.path.join(
'module',
'android',
featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
), ephemeralDirectory);
- _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
+ await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
}
- void _overwriteFromTemplate(String path, Directory target) {
- final Template template = Template.fromName(path);
+ Future<void> _overwriteFromTemplate(String path, Directory target) async {
+ final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render(
target,
<String, dynamic>{
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index c417200..208a34b 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -803,7 +803,7 @@
// Validate the current package map only if we will not be running "pub get" later.
if (parent?.name != 'pub' && !(_usesPubOption && boolArg('pub'))) {
- final String error = PackageMap(PackageMap.globalPackagesPath).checkValid();
+ final String error = PackageMap(PackageMap.globalPackagesPath, fileSystem: globals.fs).checkValid();
if (error != null) {
throw ToolExit(error);
}
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index ac37ea0..b3e6971 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -354,7 +354,7 @@
if (engineSourcePath == null && globalResults['local-engine'] != null) {
try {
- Uri engineUri = PackageMap(PackageMap.globalPackagesPath).map[kFlutterEnginePackageName];
+ Uri engineUri = PackageMap(PackageMap.globalPackagesPath, fileSystem: globals.fs).map[kFlutterEnginePackageName];
// Skip if sky_engine is the self-contained one.
if (engineUri != null && globals.fs.identicalSync(globals.fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'pkg', kFlutterEnginePackageName, 'lib'), engineUri.path)) {
engineUri = null;
@@ -491,7 +491,7 @@
// Check that the flutter running is that same as the one referenced in the pubspec.
if (globals.fs.isFileSync(kPackagesFileName)) {
- final PackageMap packageMap = PackageMap(kPackagesFileName);
+ final PackageMap packageMap = PackageMap(kPackagesFileName, fileSystem: globals.fs);
Uri flutterUri;
try {
flutterUri = packageMap.map['flutter'];
diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart
index b9beecc..499c8ab 100644
--- a/packages/flutter_tools/lib/src/template.dart
+++ b/packages/flutter_tools/lib/src/template.dart
@@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:meta/meta.dart';
+
import 'base/common.dart';
import 'base/file_system.dart';
import 'cache.dart';
-import 'globals.dart' as globals;
+import 'dart/package_map.dart';
+import 'dart/pub.dart';
+import 'globals.dart' as globals hide fs;
/// Expands templates in a directory to a destination. All files that must
-/// undergo template expansion should end with the '.tmpl' extension. All other
+/// undergo template expansion should end with the '.tmpl' extension. All files
+/// that should be replaced with the corresponding image from
+/// flutter_template_images should end with the '.img.tmpl' extension. All other
/// files are ignored. In case the contents of entire directories must be copied
/// as is, the directory itself can end with '.tmpl' extension. Files within
-/// such a directory may also contain the '.tmpl' extension and will be
-/// considered for expansion. In case certain files need to be copied but
-/// without template expansion (images, data files, etc.), the '.copy.tmpl'
+/// such a directory may also contain the '.tmpl' or '.img.tmpl' extensions and
+/// will be considered for expansion. In case certain files need to be copied
+/// but without template expansion (data files, etc.), the '.copy.tmpl'
/// extension may be used.
///
/// Folders with platform/language-specific content must be named
/// '<platform>-<language>.tmpl'.
///
-/// Files in the destination will contain none of the '.tmpl', '.copy.tmpl'
-/// or '-<language>.tmpl' extensions.
+/// Files in the destination will contain none of the '.tmpl', '.copy.tmpl',
+/// 'img.tmpl', or '-<language>.tmpl' extensions.
class Template {
- Template(Directory templateSource, Directory baseDir) {
+ Template(Directory templateSource, Directory baseDir, this.imageSourceDir, {
+ @required FileSystem fileSystem,
+ }) : _fileSystem = fileSystem {
_templateFilePaths = <String, String>{};
if (!templateSource.existsSync()) {
@@ -37,27 +45,31 @@
continue;
}
- final String relativePath = globals.fs.path.relative(entity.path,
+ final String relativePath = fileSystem.path.relative(entity.path,
from: baseDir.absolute.path);
if (relativePath.contains(templateExtension)) {
// If '.tmpl' appears anywhere within the path of this entity, it is
// is a candidate for rendering. This catches cases where the folder
// itself is a template.
- _templateFilePaths[relativePath] = globals.fs.path.absolute(entity.path);
+ _templateFilePaths[relativePath] = fileSystem.path.absolute(entity.path);
}
}
}
- factory Template.fromName(String name) {
+ static Future<Template> fromName(String name, { @required FileSystem fileSystem }) async {
// All named templates are placed in the 'templates' directory
- final Directory templateDir = templateDirectoryInPackage(name);
- return Template(templateDir, templateDir);
+ final Directory templateDir = _templateDirectoryInPackage(name, fileSystem);
+ final Directory imageDir = await _templateImageDirectory(name, fileSystem);
+ return Template(templateDir, templateDir, imageDir, fileSystem: fileSystem);
}
+ final FileSystem _fileSystem;
static const String templateExtension = '.tmpl';
static const String copyTemplateExtension = '.copy.tmpl';
+ static const String imageTemplateExtension = '.img.tmpl';
final Pattern _kTemplateLanguageVariant = RegExp(r'(\w+)-(\w+)\.tmpl.*');
+ final Directory imageSourceDir;
Map<String /* relative */, String /* absolute source */> _templateFilePaths;
@@ -109,14 +121,20 @@
if (relativeDestinationPath.startsWith('macos.tmpl') && !macOS) {
return null;
}
+ // Only build a Windows project if explicitly asked.
+ final bool windows = context['windows'] as bool;
+ if (relativeDestinationPath.startsWith('windows.tmpl') && !windows) {
+ return null;
+ }
final String projectName = context['projectName'] as String;
final String androidIdentifier = context['androidIdentifier'] as String;
final String pluginClass = context['pluginClass'] as String;
final String destinationDirPath = destination.absolute.path;
- final String pathSeparator = globals.fs.path.separator;
- String finalDestinationPath = globals.fs.path
+ final String pathSeparator = _fileSystem.path.separator;
+ String finalDestinationPath = _fileSystem.path
.join(destinationDirPath, relativeDestinationPath)
.replaceAll(copyTemplateExtension, '')
+ .replaceAll(imageTemplateExtension, '')
.replaceAll(templateExtension, '');
if (androidIdentifier != null) {
@@ -142,8 +160,8 @@
if (finalDestinationPath == null) {
return;
}
- final File finalDestinationFile = globals.fs.file(finalDestinationPath);
- final String relativePathForLogging = globals.fs.path.relative(finalDestinationFile.path);
+ final File finalDestinationFile = _fileSystem.file(finalDestinationPath);
+ final String relativePathForLogging = _fileSystem.path.relative(finalDestinationFile.path);
// Step 1: Check if the file needs to be overwritten.
@@ -169,7 +187,7 @@
fileCount++;
finalDestinationFile.createSync(recursive: true);
- final File sourceFile = globals.fs.file(absoluteSourcePath);
+ final File sourceFile = _fileSystem.file(absoluteSourcePath);
// Step 2: If the absolute paths ends with a '.copy.tmpl', this file does
// not need mustache rendering but needs to be directly copied.
@@ -180,7 +198,18 @@
return;
}
- // Step 3: If the absolute path ends with a '.tmpl', this file needs
+ // Step 3: If the absolute paths ends with a '.img.tmpl', this file needs
+ // to be copied from the template image package.
+
+ if (sourceFile.path.endsWith(imageTemplateExtension)) {
+ final File imageSourceFile = _fileSystem.file(_fileSystem.path.join(
+ imageSourceDir.path, relativeDestinationPath.replaceAll(imageTemplateExtension, '')));
+ imageSourceFile.copySync(finalDestinationFile.path);
+
+ return;
+ }
+
+ // Step 4: If the absolute path ends with a '.tmpl', this file needs
// rendering via mustache.
if (sourceFile.path.endsWith(templateExtension)) {
@@ -192,7 +221,7 @@
return;
}
- // Step 4: This file does not end in .tmpl but is in a directory that
+ // Step 5: This file does not end in .tmpl but is in a directory that
// does. Directly copy the file to the destination.
sourceFile.copySync(finalDestinationFile.path);
@@ -202,8 +231,39 @@
}
}
-Directory templateDirectoryInPackage(String name) {
- final String templatesDir = globals.fs.path.join(Cache.flutterRoot,
+Directory _templateDirectoryInPackage(String name, FileSystem fileSystem) {
+ final String templatesDir = fileSystem.path.join(Cache.flutterRoot,
'packages', 'flutter_tools', 'templates');
- return globals.fs.directory(globals.fs.path.join(templatesDir, name));
+ return fileSystem.directory(fileSystem.path.join(templatesDir, name));
+}
+
+// Returns the directory containing the 'name' template directory in
+// flutter_template_images, to resolve image placeholder against.
+Future<Directory> _templateImageDirectory(String name, FileSystem fileSystem) async {
+ final String toolPackagePath = fileSystem.path.join(
+ Cache.flutterRoot, 'packages', 'flutter_tools');
+ final String packageFilePath = fileSystem.path.join(toolPackagePath, kPackagesFileName);
+ // Ensure that .packgaes is present.
+ if (!fileSystem.file(packageFilePath).existsSync()) {
+ await _ensurePackageDependencies(toolPackagePath);
+ }
+ final PackageMap packageConfig = PackageMap(packageFilePath, fileSystem: fileSystem);
+ final Uri imagePackageLibDir = packageConfig.map['flutter_template_images'];
+ // Ensure that the template image package is present.
+ if (imagePackageLibDir == null || !fileSystem.directory(imagePackageLibDir).existsSync()) {
+ await _ensurePackageDependencies(toolPackagePath);
+ }
+ return fileSystem.directory(imagePackageLibDir)
+ .parent
+ .childDirectory('templates')
+ .childDirectory(name);
+}
+
+// Runs 'pub get' for the given path to ensure that .packages is created and
+// all dependencies are present.
+Future<void> _ensurePackageDependencies(String packagePath) async {
+ await pub.get(
+ context: PubContext.pubGet,
+ directory: packagePath,
+ );
}
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
index de39ee8..b49ef95 100644
--- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
@@ -111,7 +111,7 @@
'packages',
'flutter_tools',
'.packages',
- ));
+ ), fileSystem: globals.fs);
/// Uri of the test package.
Uri get testUri => _flutterToolsPackageMap.map['test'];
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index 85bb09a..77b23a3 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -27,6 +27,20 @@
'to learn about adding Windows support to a project.');
}
+ // Check for incompatibility between the Flutter tool version and the project
+ // template version, since the tempalte isn't stable yet.
+ final int templateCompareResult = _compareTemplateVersions(windowsProject);
+ if (templateCompareResult < 0) {
+ throwToolExit('The Windows runner was created with an earlier version of '
+ 'the template, which is not yet stable.\n\n'
+ 'Delete the windows/ directory and re-run \'flutter create .\', '
+ 're-applying any previous changes.');
+ } else if (templateCompareResult > 0) {
+ throwToolExit('The Windows runner was created with a newer version of the '
+ 'template, which is not yet stable.\n\n'
+ 'Upgrade Flutter and try again.');
+ }
+
// Ensure that necessary emphemeral files are generated and up to date.
_writeGeneratedFlutterProperties(windowsProject, buildInfo, target);
createPluginSymlinks(windowsProject.project);
@@ -109,3 +123,25 @@
propsFile.createSync(recursive: true);
propsFile.writeAsStringSync(PropertySheet(environmentVariables: environment).toString());
}
+
+// Checks the template version of [project] against the current template
+// version. Returns < 0 if the project is older than the current template, > 0
+// if it's newer, and 0 if they match.
+int _compareTemplateVersions(WindowsProject project) {
+ const String projectVersionBasename = '.template_version';
+ final int expectedVersion = int.parse(globals.fs.file(globals.fs.path.join(
+ globals.fs.path.absolute(Cache.flutterRoot),
+ 'packages',
+ 'flutter_tools',
+ 'templates',
+ 'app',
+ 'windows.tmpl',
+ 'flutter',
+ projectVersionBasename,
+ )).readAsStringSync());
+ final File projectVersionFile = project.managedDirectory.childFile(projectVersionBasename);
+ final int version = projectVersionFile.existsSync()
+ ? int.tryParse(projectVersionFile.readAsStringSync())
+ : 0;
+ return version.compareTo(expectedVersion);
+}
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index 4c50f96..bbc4c39 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -16,6 +16,7 @@
coverage: 0.13.9
crypto: 2.1.3
file: 5.1.0
+ flutter_template_images: 1.0.0
http: 0.12.0+4
intl: 0.16.1
json_rpc_2: 2.1.0
@@ -111,4 +112,4 @@
# Exclude this package from the hosted API docs.
nodoc: true
-# PUBSPEC CHECKSUM: ce97
+# PUBSPEC CHECKSUM: 8b7f
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/.gitignore b/packages/flutter_tools/templates/app/windows.tmpl/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/AppConfiguration.props.tmpl b/packages/flutter_tools/templates/app/windows.tmpl/AppConfiguration.props.tmpl
new file mode 100644
index 0000000..9ba9108
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/AppConfiguration.props.tmpl
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <TargetName>{{projectName}}</TargetName>
+ </PropertyGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/FlutterBuild.vcxproj b/packages/flutter_tools/templates/app/windows.tmpl/FlutterBuild.vcxproj
new file mode 100644
index 0000000..8ef93f9
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/FlutterBuild.vcxproj
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Profile|x64">
+ <Configuration>Profile</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>15.0</VCProjectVersion>
+ <ProjectGuid>{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}</ProjectGuid>
+ <ProjectName>Flutter Build</ProjectName>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration">
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="flutter\ephemeral\Generated.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup />
+ <ItemDefinitionGroup>
+ <CustomBuildStep>
+ <Command>"$(ProjectDir)scripts\prepare_dependencies" $(Configuration)</Command>
+ <Message>Running Flutter backend build</Message>
+ <Outputs>force_to_run_every_time</Outputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/Runner.rc b/packages/flutter_tools/templates/app/windows.tmpl/Runner.rc
new file mode 100644
index 0000000..5b41a82
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/Runner.rc
@@ -0,0 +1,70 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON ICON "resources\\app_icon.ico"
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/Runner.sln b/packages/flutter_tools/templates/app/windows.tmpl/Runner.sln
new file mode 100644
index 0000000..00fb3d3
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/Runner.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29709.97
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{5A827760-CF8B-408A-99A3-B6C0AD2271E7}"
+ ProjectSection(ProjectDependencies) = postProject
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Flutter Build", "FlutterBuild.vcxproj", "{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Profile|x64 = Profile|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.ActiveCfg = Debug|x64
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.Build.0 = Debug|x64
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.ActiveCfg = Profile|x64
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.Build.0 = Profile|x64
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.ActiveCfg = Release|x64
+ {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.Build.0 = Release|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.Build.0 = Debug|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.ActiveCfg = Profile|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.Build.0 = Profile|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.ActiveCfg = Release|x64
+ {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B8A69CB0-A974-4774-9EBD-1E5EECACD186}
+ EndGlobalSection
+EndGlobal
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.filters b/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.filters
new file mode 100644
index 0000000..5d3499c
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.filters
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Source Files\Client Wrapper">
+ <UniqueIdentifier>{2761a4b5-57b2-4d50-a677-d20ddc17a7f1}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="flutter\generated_plugin_registrant.cc">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win32_window.cc">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="window_configuration.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc">
+ <Filter>Source Files\Client Wrapper</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\flutter_view_controller.cc">
+ <Filter>Source Files\Client Wrapper</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc">
+ <Filter>Source Files\Client Wrapper</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="flutter\generated_plugin_registrant.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win32_window.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="window_configuration.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Manifest Include="runner.exe.manifest" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="Runner.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\app_icon.ico">
+ <Filter>Resource Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.tmpl b/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.tmpl
new file mode 100644
index 0000000..b4e72ac
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.tmpl
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Profile|x64">
+ <Configuration>Profile</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>15.0</VCProjectVersion>
+ <ProjectGuid>{5A827760-CF8B-408A-99A3-B6C0AD2271E7}</ProjectGuid>
+ <RootNamespace>{{projectName}}</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="flutter\ephemeral\Generated.props" />
+ <Import Project="AppConfiguration.props" />
+ <Import Project="flutter\GeneratedPlugins.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="flutter\ephemeral\Generated.props" />
+ <Import Project="AppConfiguration.props" />
+ <Import Project="flutter\GeneratedPlugins.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="flutter\ephemeral\Generated.props" />
+ <Import Project="AppConfiguration.props" />
+ <Import Project="flutter\GeneratedPlugins.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
+ <IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
+ <IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
+ <OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
+ <IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <PreLinkEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreLinkEvent>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ <CustomBuildStep>
+ <Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)"</Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>Bundling dependencies</Message>
+ <Outputs>Dummy_Run_Always</Outputs>
+ <Inputs>
+ </Inputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <PreLinkEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreLinkEvent>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ <CustomBuildStep>
+ <Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)"</Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>Bundling dependencies</Message>
+ <Outputs>Dummy_Run_Always</Outputs>
+ <Inputs>
+ </Inputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
+ <ClCompile>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ </ClCompile>
+ <Link>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <PreLinkEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreLinkEvent>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ <CustomBuildStep>
+ <Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)"</Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>Bundling dependencies</Message>
+ <Outputs>Dummy_Run_Always</Outputs>
+ <Inputs>
+ </Inputs>
+ </CustomBuildStep>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="flutter\generated_plugin_registrant.cc" />
+ <ClCompile Include="window_configuration.cpp" />
+ <ClCompile Include="win32_window.cc" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\flutter_view_controller.cc" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="flutter\generated_plugin_registrant.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="win32_window.h" />
+ <ClInclude Include="window_configuration.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Manifest Include="runner.exe.manifest" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="Runner.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\app_icon.ico" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+ <PropertyGroup>
+ <LocalDebuggerWorkingDirectory>$(SolutionDir)</LocalDebuggerWorkingDirectory>
+ </PropertyGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/flutter/.template_version b/packages/flutter_tools/templates/app/windows.tmpl/flutter/.template_version
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/flutter/.template_version
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/main.cpp b/packages/flutter_tools/templates/app/windows.tmpl/main.cpp
new file mode 100644
index 0000000..f2e4301
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/main.cpp
@@ -0,0 +1,100 @@
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include <chrono>
+#include <codecvt>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "flutter/generated_plugin_registrant.h"
+#include "win32_window.h"
+#include "window_configuration.h"
+
+namespace {
+
+// Returns the path of the directory containing this executable, or an empty
+// string if the directory cannot be found.
+std::string GetExecutableDirectory() {
+ wchar_t buffer[MAX_PATH];
+ if (GetModuleFileName(nullptr, buffer, MAX_PATH) == 0) {
+ std::cerr << "Couldn't locate executable" << std::endl;
+ return "";
+ }
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wide_to_utf8;
+ std::string executable_path = wide_to_utf8.to_bytes(buffer);
+ size_t last_separator_position = executable_path.find_last_of('\\');
+ if (last_separator_position == std::string::npos) {
+ std::cerr << "Unabled to find parent directory of " << executable_path
+ << std::endl;
+ return "";
+ }
+ return executable_path.substr(0, last_separator_position);
+}
+
+} // namespace
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+ _In_ wchar_t *command_line, _In_ int show_command) {
+ // Attach to console when present (e.g., 'flutter run') or create a
+ // new console when running with a debugger.
+ if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+ ::AllocConsole();
+ }
+
+ // Resources are located relative to the executable.
+ std::string base_directory = GetExecutableDirectory();
+ if (base_directory.empty()) {
+ base_directory = ".";
+ }
+ std::string data_directory = base_directory + "\\data";
+ std::string assets_path = data_directory + "\\flutter_assets";
+ std::string icu_data_path = data_directory + "\\icudtl.dat";
+
+ // Arguments for the Flutter Engine.
+ std::vector<std::string> arguments;
+
+ // Top-level window frame.
+ Win32Window::Point origin(kFlutterWindowOriginX, kFlutterWindowOriginY);
+ Win32Window::Size size(kFlutterWindowWidth, kFlutterWindowHeight);
+
+ flutter::FlutterViewController flutter_controller(
+ icu_data_path, size.width, size.height, assets_path, arguments);
+ RegisterPlugins(&flutter_controller);
+
+ // Create a top-level win32 window to host the Flutter view.
+ Win32Window window;
+ if (!window.CreateAndShow(kFlutterWindowTitle, origin, size)) {
+ return EXIT_FAILURE;
+ }
+
+ // Parent and resize Flutter view into top-level window.
+ window.SetChildContent(flutter_controller.view()->GetNativeWindow());
+
+ // Run messageloop with a hook for flutter_controller to do work until
+ // the window is closed.
+ std::chrono::nanoseconds wait_duration(0);
+ // Run until the window is closed.
+ while (window.GetHandle() != nullptr) {
+ MsgWaitForMultipleObjects(0, nullptr, FALSE,
+ static_cast<DWORD>(wait_duration.count() / 1000),
+ QS_ALLINPUT);
+ MSG message;
+ // All pending Windows messages must be processed; MsgWaitForMultipleObjects
+ // won't return again for items left in the queue after PeekMessage.
+ while (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+ if (message.message == WM_QUIT) {
+ window.Destroy();
+ break;
+ }
+ TranslateMessage(&message);
+ DispatchMessage(&message);
+ }
+ // Allow Flutter to process its messages.
+ // TODO: Consider interleaving processing on a per-message basis to avoid
+ // the possibility of one queue starving the other.
+ wait_duration = flutter_controller.ProcessMessages();
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/resource.h b/packages/flutter_tools/templates/app/windows.tmpl/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/resources/app_icon.ico.img.tmpl b/packages/flutter_tools/templates/app/windows.tmpl/resources/app_icon.ico.img.tmpl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/resources/app_icon.ico.img.tmpl
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/runner.exe.manifest b/packages/flutter_tools/templates/app/windows.tmpl/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+ </windowsSettings>
+ </application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/scripts/bundle_assets_and_deps.bat b/packages/flutter_tools/templates/app/windows.tmpl/scripts/bundle_assets_and_deps.bat
new file mode 100644
index 0000000..25ae1c4
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/scripts/bundle_assets_and_deps.bat
@@ -0,0 +1,37 @@
+@echo off
+
+set FLUTTER_CACHE_DIR=%~1
+set BUNDLE_DIR=%~2
+set PLUGIN_DIR=%~3
+set EXE_NAME=%~4
+
+set DATA_DIR=%BUNDLE_DIR%data
+
+if not exist "%DATA_DIR%" call mkdir "%DATA_DIR%"
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+:: Write the executable name to the location expected by the Flutter tool.
+echo %EXE_NAME%>"%FLUTTER_CACHE_DIR%exe_filename"
+
+:: Copy the Flutter assets to the data directory.
+set FLUTTER_APP_DIR=%~dp0..\..
+set ASSET_DIR_NAME=flutter_assets
+set TARGET_ASSET_DIR=%DATA_DIR%\%ASSET_DIR_NAME%
+if exist "%TARGET_ASSET_DIR%" call rmdir /s /q "%TARGET_ASSET_DIR%"
+if %errorlevel% neq 0 exit /b %errorlevel%
+call xcopy /s /e /i /q "%FLUTTER_APP_DIR%\build\%ASSET_DIR_NAME%" "%TARGET_ASSET_DIR%"
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+:: Copy the icudtl.dat file from the Flutter tree to the data directory.
+call xcopy /y /d /q "%FLUTTER_CACHE_DIR%icudtl.dat" "%DATA_DIR%"
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+:: Copy the Flutter DLL to the target location.
+call xcopy /y /d /q "%FLUTTER_CACHE_DIR%flutter_windows.dll" "%BUNDLE_DIR%"
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+:: Copy any Plugin DLLs to the target location.
+if exist "%PLUGIN_DIR%" (
+ call xcopy /y /d /q "%PLUGIN_DIR%"*.dll "%BUNDLE_DIR%"
+ if %errorlevel% neq 0 exit /b %errorlevel%
+)
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/scripts/prepare_dependencies.bat b/packages/flutter_tools/templates/app/windows.tmpl/scripts/prepare_dependencies.bat
new file mode 100644
index 0000000..d05238b
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/scripts/prepare_dependencies.bat
@@ -0,0 +1,5 @@
+@echo off
+
+:: Run flutter tool backend.
+set BUILD_MODE=%~1
+"%FLUTTER_ROOT%\packages\flutter_tools\bin\tool_backend" windows-x64 %BUILD_MODE%
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/win32_window.cc b/packages/flutter_tools/templates/app/windows.tmpl/win32_window.cc
new file mode 100644
index 0000000..a8037b7
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/win32_window.cc
@@ -0,0 +1,177 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+// The Windows DPI system is based on this
+// constant for machines running at 100% scaling.
+constexpr int kBaseDpi = 96;
+
+constexpr const wchar_t kClassName[] = L"CLASSNAME";
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+ return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+ HMODULE user32_module = LoadLibraryA("User32.dll");
+ if (!user32_module) {
+ return;
+ }
+ auto enable_non_client_dpi_scaling =
+ reinterpret_cast<EnableNonClientDpiScaling *>(
+ GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+ if (enable_non_client_dpi_scaling != nullptr) {
+ enable_non_client_dpi_scaling(hwnd);
+ FreeLibrary(user32_module);
+ }
+}
+} // namespace
+
+Win32Window::Win32Window() {}
+
+Win32Window::~Win32Window() { Destroy(); }
+
+bool Win32Window::CreateAndShow(const std::wstring &title, const Point &origin,
+ const Size &size) {
+ Destroy();
+
+ WNDCLASS window_class = RegisterWindowClass();
+
+ const POINT target_point = {static_cast<LONG>(origin.x),
+ static_cast<LONG>(origin.y)};
+ HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+ UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+ double scale_factor = dpi / kBaseDpi;
+
+ HWND window = CreateWindow(
+ window_class.lpszClassName, title.c_str(),
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor),
+ Scale(origin.y, scale_factor), Scale(size.width, scale_factor),
+ Scale(size.height, scale_factor), nullptr, nullptr,
+ window_class.hInstance, this);
+ return window != nullptr;
+}
+
+WNDCLASS Win32Window::RegisterWindowClass() {
+ WNDCLASS window_class{};
+ window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ window_class.lpszClassName = kClassName;
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = GetModuleHandle(nullptr);
+ window_class.hIcon =
+ LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+ window_class.hbrBackground = 0;
+ window_class.lpszMenuName = nullptr;
+ window_class.lpfnWndProc = WndProc;
+ RegisterClass(&window_class);
+ return window_class;
+}
+
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ if (message == WM_NCCREATE) {
+ auto window_struct = reinterpret_cast<CREATESTRUCT *>(lparam);
+ SetWindowLongPtr(window, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+ auto that = static_cast<Win32Window *>(window_struct->lpCreateParams);
+ EnableFullDpiSupportIfAvailable(window);
+ that->window_handle_ = window;
+ } else if (Win32Window *that = GetThisFromHandle(window)) {
+ return that->MessageHandler(window, message, wparam, lparam);
+ }
+
+ return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ auto window =
+ reinterpret_cast<Win32Window *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+
+ if (window == nullptr) {
+ return 0;
+ }
+
+ switch (message) {
+ case WM_DESTROY:
+ window_handle_ = nullptr;
+ Destroy();
+ return 0;
+
+ case WM_DPICHANGED: {
+ auto newRectSize = reinterpret_cast<RECT *>(lparam);
+ LONG newWidth = newRectSize->right - newRectSize->left;
+ LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+ SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+ newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+ return 0;
+ }
+ case WM_SIZE:
+ RECT rect;
+ GetClientRect(hwnd, &rect);
+ if (child_content_ != nullptr) {
+ // Size and position the child window.
+ MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+ rect.bottom - rect.top, TRUE);
+ }
+ return 0;
+
+ case WM_ACTIVATE:
+ if (child_content_ != nullptr) {
+ SetFocus(child_content_);
+ }
+ return 0;
+
+ // Messages that are directly forwarded to embedding.
+ case WM_FONTCHANGE:
+ SendMessage(child_content_, WM_FONTCHANGE, NULL, NULL);
+ return 0;
+ }
+
+ return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+ if (window_handle_) {
+ DestroyWindow(window_handle_);
+ window_handle_ = nullptr;
+ }
+
+ UnregisterClass(kClassName, nullptr);
+}
+
+Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept {
+ return reinterpret_cast<Win32Window *>(
+ GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+ child_content_ = content;
+ SetParent(content, window_handle_);
+ RECT frame;
+ GetClientRect(window_handle_, &frame);
+
+ MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+ frame.bottom - frame.top, true);
+
+ SetFocus(child_content_);
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/win32_window.h b/packages/flutter_tools/templates/app/windows.tmpl/win32_window.h
new file mode 100644
index 0000000..badbc13
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/win32_window.h
@@ -0,0 +1,86 @@
+#ifndef WIN32_WINDOW_H_
+#define WIN32_WINDOW_H_
+
+#include <Windows.h>
+#include <Windowsx.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+ struct Point {
+ unsigned int x;
+ unsigned int y;
+ Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+ };
+
+ struct Size {
+ unsigned int width;
+ unsigned int height;
+ Size(unsigned int width, unsigned int height)
+ : width(width), height(height) {}
+ };
+
+ Win32Window();
+ virtual ~Win32Window();
+
+ // Creates and shows a win32 window with |title| and position and size using
+ // |origin| and |size|. New windows are created on the default monitor. Window
+ // sizes are specified to the OS in physical pixels, hence to ensure a
+ // consistent size to will treat the width height passed in to this function
+ // as logical pixels and scale to appropriate for the default monitor. Returns
+ // true if the window was created successfully.
+ bool CreateAndShow(const std::wstring &title, const Point &origin,
+ const Size &size);
+
+ // Release OS resources asociated with window.
+ void Destroy();
+
+ // Inserts |content| into the window tree.
+ void SetChildContent(HWND content);
+
+ // Returns the backing Window handle to enable clients to set icon and other
+ // window properties. Returns nullptr if the window has been destroyed.
+ HWND GetHandle();
+
+ protected:
+ // Registers a window class with default style attributes, cursor and
+ // icon.
+ WNDCLASS RegisterWindowClass();
+
+ // OS callback called by message pump. Handles the WM_NCCREATE message which
+ // is passed when the non-client area is being created and enables automatic
+ // non-client DPI scaling so that the non-client area automatically
+ // responsponds to changes in DPI. All other messages are handled by
+ // MessageHandler.
+ static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Processes and route salient window messages for mouse handling,
+ // size change and DPI. Delegates handling of these to member overloads that
+ // inheriting classes can handle.
+ LRESULT
+ MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ private:
+ // should message loop keep running
+ bool messageloop_running_ = true;
+
+ // Retrieves a class instance pointer for |window|
+ static Win32Window *GetThisFromHandle(HWND const window) noexcept;
+
+ // window handle for top level window.
+ HWND window_handle_ = nullptr;
+
+ // window handle for hosted content.
+ HWND child_content_ = nullptr;
+};
+
+#endif // WIN32_WINDOW_H_
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.cpp.tmpl b/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.cpp.tmpl
new file mode 100644
index 0000000..31082b5
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.cpp.tmpl
@@ -0,0 +1,7 @@
+#include "window_configuration.h"
+
+const wchar_t *kFlutterWindowTitle = L"{{projectName}}";
+const unsigned int kFlutterWindowOriginX = 10;
+const unsigned int kFlutterWindowOriginY = 10;
+const unsigned int kFlutterWindowWidth = 800;
+const unsigned int kFlutterWindowHeight = 600;
diff --git a/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.h b/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.h
new file mode 100644
index 0000000..96727f6
--- /dev/null
+++ b/packages/flutter_tools/templates/app/windows.tmpl/window_configuration.h
@@ -0,0 +1,18 @@
+#ifndef WINDOW_CONFIGURATION_
+#define WINDOW_CONFIGURATION_
+
+// This is a temporary approach to isolate changes that people are likely to
+// make to main.cpp, where the APIs are still in flux. This will reduce the
+// need to resolve conflicts or re-create changes slightly differently every
+// time the Windows Flutter API surface changes.
+//
+// Longer term there should be simpler configuration options for common
+// customizations like this, without requiring native code changes.
+
+extern const wchar_t *kFlutterWindowTitle;
+extern const unsigned int kFlutterWindowOriginX;
+extern const unsigned int kFlutterWindowOriginY;
+extern const unsigned int kFlutterWindowWidth;
+extern const unsigned int kFlutterWindowHeight;
+
+#endif // WINDOW_CONFIGURATION_
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
index f5a4f6c..aab588b 100644
--- a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
+++ b/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
@@ -1,4 +1,4 @@
-#include "sample_plugin.h"
+#include "{{projectName}}_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_glfw.h>
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/.gitignore b/packages/flutter_tools/templates/plugin/windows.tmpl/.gitignore
new file mode 100644
index 0000000..b3eb2be
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/.gitignore
@@ -0,0 +1,17 @@
+flutter/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/PluginInfo.props.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/PluginInfo.props.tmpl
new file mode 100644
index 0000000..636a88b
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/PluginInfo.props.tmpl
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ImportGroup Label="PropertySheets" />
+ <PropertyGroup Label="UserMacros">
+ <FlutterPluginName>{{projectName}}</FlutterPluginName>
+ </PropertyGroup>
+ <PropertyGroup />
+ <ItemDefinitionGroup />
+ <ItemGroup>
+ <BuildMacro Include="FlutterPluginName">
+ <Value>$(FlutterPluginName)</Value>
+ </BuildMacro>
+ </ItemGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.filters b/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.filters
new file mode 100644
index 0000000..0f8c708
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.filters
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Source Files\cpp_client_wrapper">
+ <UniqueIdentifier>{dbe2dac9-4a21-4849-bef5-2069d695609d}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="stdafx.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="targetver.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="$(FlutterPluginName)_plugin.h">
+ <Filter>Source Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc">
+ <Filter>Source Files\cpp_client_wrapper</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\standard_codec.cc">
+ <Filter>Source Files\cpp_client_wrapper</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc">
+ <Filter>Source Files\cpp_client_wrapper</Filter>
+ </ClCompile>
+ <ClCompile Include="$(FlutterPluginName)_plugin.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.tmpl
new file mode 100644
index 0000000..c9e56be
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.tmpl
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Profile|x64">
+ <Configuration>Profile</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>15.0</VCProjectVersion>
+ <ProjectGuid>{{{pluginProjectUUID}}}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>{{projectName}}</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ <ProjectName>{{projectName}}</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
+ <Import Project="PluginInfo.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
+ <Import Project="PluginInfo.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
+ <Import Project="PluginInfo.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
+ <IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ <CustomBuildBeforeTargets>
+ </CustomBuildBeforeTargets>
+ <TargetName>$(ProjectName)_plugin</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
+ <IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ <CustomBuildBeforeTargets>
+ </CustomBuildBeforeTargets>
+ <TargetName>$(ProjectName)_plugin</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
+ <IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
+ <CustomBuildBeforeTargets />
+ <TargetName>$(ProjectName)_plugin</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <ExceptionHandling>false</ExceptionHandling>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <CustomBuildStep>
+ <Command>
+ </Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>
+ </Message>
+ <Outputs>
+ </Outputs>
+ </CustomBuildStep>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <ExceptionHandling>false</ExceptionHandling>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <CustomBuildStep>
+ <Command>
+ </Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>
+ </Message>
+ <Outputs>
+ </Outputs>
+ </CustomBuildStep>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
+ <ClCompile>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <ExceptionHandling>false</ExceptionHandling>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PreBuildEvent>
+ <Command>
+ </Command>
+ <Message>
+ </Message>
+ </PreBuildEvent>
+ <CustomBuildStep>
+ <Command>
+ </Command>
+ </CustomBuildStep>
+ <CustomBuildStep>
+ <Message>
+ </Message>
+ <Outputs>
+ </Outputs>
+ </CustomBuildStep>
+ <PostBuildEvent>
+ <Command>
+ </Command>
+ </PostBuildEvent>
+ <PostBuildEvent>
+ <Message>
+ </Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="$(FlutterPluginName)_plugin.h" />
+ <ClInclude Include="stdafx.h" />
+ <ClInclude Include="targetver.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="$(FlutterPluginName)_plugin.cpp" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc" />
+ <ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\standard_codec.cc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl
new file mode 100644
index 0000000..a26aee4
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl
@@ -0,0 +1,95 @@
+#include "{{projectName}}_plugin.h"
+
+// This must be included before many other Windows headers.
+#include <windows.h>
+
+// For getPlatformVersion; remove unless needed for your plugin implementation.
+#include <VersionHelpers.h>
+
+#include <flutter/method_channel.h>
+#include <flutter/plugin_registrar_windows.h>
+#include <flutter/standard_method_codec.h>
+
+#include <map>
+#include <memory>
+#include <sstream>
+
+namespace {
+
+class {{pluginClass}} : public flutter::Plugin {
+ public:
+ static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
+
+ {{pluginClass}}();
+
+ virtual ~{{pluginClass}}();
+
+ private:
+ // Called when a method is called on this plugin's channel from Dart.
+ void HandleMethodCall(
+ const flutter::MethodCall<flutter::EncodableValue> &method_call,
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
+};
+
+// static
+void {{pluginClass}}::RegisterWithRegistrar(
+ flutter::PluginRegistrarWindows *registrar) {
+ auto channel =
+ std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
+ registrar->messenger(), "{{projectName}}",
+ &flutter::StandardMethodCodec::GetInstance());
+
+ auto plugin = std::make_unique<{{pluginClass}}>();
+
+ channel->SetMethodCallHandler(
+ [plugin_pointer = plugin.get()](const auto &call, auto result) {
+ plugin_pointer->HandleMethodCall(call, std::move(result));
+ });
+
+ registrar->AddPlugin(std::move(plugin));
+}
+
+{{pluginClass}}::{{pluginClass}}() {}
+
+{{pluginClass}}::~{{pluginClass}}() {}
+
+void {{pluginClass}}::HandleMethodCall(
+ const flutter::MethodCall<flutter::EncodableValue> &method_call,
+ std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
+ // Replace "getPlatformVersion" check with your plugin's method.
+ // See:
+ // https://github.com/flutter/engine/tree/master/shell/platform/common/cpp/client_wrapper/include/flutter
+ // and
+ // https://github.com/flutter/engine/tree/master/shell/platform/glfw/client_wrapper/include/flutter
+ // for the relevant Flutter APIs.
+ if (method_call.method_name().compare("getPlatformVersion") == 0) {
+ std::ostringstream version_stream;
+ version_stream << "Windows ";
+ if (IsWindows10OrGreater()) {
+ version_stream << "10+";
+ } else if (IsWindows8OrGreater()) {
+ version_stream << "8";
+ } else if (IsWindows7OrGreater()) {
+ version_stream << "7";
+ }
+ flutter::EncodableValue response(version_stream.str());
+ result->Success(&response);
+ } else {
+ result->NotImplemented();
+ }
+}
+
+} // namespace
+
+void {{pluginClass}}RegisterWithRegistrar(
+ FlutterDesktopPluginRegistrarRef registrar) {
+ // The plugin registrar wrappers owns the plugins, registered callbacks, etc.,
+ // so must remain valid for the life of the application.
+ static auto *plugin_registrars =
+ new std::map<FlutterDesktopPluginRegistrarRef,
+ std::unique_ptr<flutter::PluginRegistrarWindows>>;
+ auto insert_result = plugin_registrars->emplace(
+ registrar, std::make_unique<flutter::PluginRegistrarWindows>(registrar));
+
+ {{pluginClass}}::RegisterWithRegistrar(insert_result.first->second.get());
+}
diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.h.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.h.tmpl
new file mode 100644
index 0000000..8691b2f
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.h.tmpl
@@ -0,0 +1,23 @@
+#ifndef FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
+#define FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
+
+#include <flutter_plugin_registrar.h>
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+FLUTTER_PLUGIN_EXPORT void {{pluginClass}}RegisterWithRegistrar(
+ FlutterDesktopPluginRegistrarRef registrar);
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+#endif // FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
index 5a96954..72179d9 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
@@ -15,13 +15,13 @@
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import 'package:xml/xml.dart' as xml;
-import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
import '../../src/testbed.dart';
+const String flutterRoot = r'C:\flutter';
const String solutionPath = r'C:\windows\Runner.sln';
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
@@ -30,17 +30,19 @@
operatingSystem: 'windows',
environment: <String, String>{
'PROGRAMFILES(X86)': r'C:\Program Files (x86)\',
- 'FLUTTER_ROOT': r'C:\',
+ 'FLUTTER_ROOT': flutterRoot,
}
);
final Platform notWindowsPlatform = FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{
- 'FLUTTER_ROOT': r'C:\',
+ 'FLUTTER_ROOT': flutterRoot,
}
);
void main() {
+ FileSystem fileSystem;
+
MockProcessManager mockProcessManager;
MockProcess mockProcess;
MockVisualStudio mockVisualStudio;
@@ -50,6 +52,8 @@
});
setUp(() {
+ fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
+ Cache.flutterRoot = flutterRoot;
mockProcessManager = MockProcessManager();
mockProcess = MockProcess();
mockVisualStudio = MockVisualStudio();
@@ -66,15 +70,35 @@
// Creates the mock files necessary to look like a Flutter project.
void setUpMockCoreProjectFiles() {
- globals.fs.file('pubspec.yaml').createSync();
- globals.fs.file('.packages').createSync();
- globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ fileSystem.file('pubspec.yaml').createSync();
+ fileSystem.file('.packages').createSync();
+ fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
}
// Creates the mock files necessary to run a build.
- void setUpMockProjectFilesForBuild() {
- globals.fs.file(solutionPath).createSync(recursive: true);
+ void setUpMockProjectFilesForBuild({int templateVersion}) {
+ fileSystem.file(solutionPath).createSync(recursive: true);
setUpMockCoreProjectFiles();
+
+ final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version');
+ const int expectedTemplateVersion = 10; // Arbitrary value for tests.
+ final File sourceTemplateVersionfile = fileSystem.file(fileSystem.path.join(
+ fileSystem.path.absolute(Cache.flutterRoot),
+ 'packages',
+ 'flutter_tools',
+ 'templates',
+ 'app',
+ 'windows.tmpl',
+ versionFileSubpath,
+ ));
+ sourceTemplateVersionfile.createSync(recursive: true);
+ sourceTemplateVersionfile.writeAsStringSync(expectedTemplateVersion.toString());
+
+ final File projectTemplateVersionFile = fileSystem.file(
+ fileSystem.path.join('windows', versionFileSubpath));
+ templateVersion ??= expectedTemplateVersion;
+ projectTemplateVersionFile.createSync(recursive: true);
+ projectTemplateVersionFile.writeAsStringSync(templateVersion.toString());
}
testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
@@ -88,7 +112,7 @@
), throwsToolExit());
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -105,7 +129,7 @@
), throwsToolExit(message: 'No Windows desktop project configured'));
}, overrides: <Type, Generator>{
Platform: () => windowsPlatform,
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -122,11 +146,43 @@
), throwsToolExit());
}, overrides: <Type, Generator>{
Platform: () => notWindowsPlatform,
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
+ testUsingContext('Windows build fails with instructions when template is too old', () async {
+ final BuildWindowsCommand command = BuildWindowsCommand()
+ ..visualStudioOverride = mockVisualStudio;
+ applyMocksToCommand(command);
+ setUpMockProjectFilesForBuild(templateVersion: 1);
+
+ expect(createTestCommandRunner(command).run(
+ const <String>['windows']
+ ), throwsToolExit(message: 'flutter create .'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Platform: () => windowsPlatform,
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+ });
+
+ testUsingContext('Windows build fails with instructions when template is too new', () async {
+ final BuildWindowsCommand command = BuildWindowsCommand()
+ ..visualStudioOverride = mockVisualStudio;
+ applyMocksToCommand(command);
+ setUpMockProjectFilesForBuild(templateVersion: 999);
+
+ expect(createTestCommandRunner(command).run(
+ const <String>['windows']
+ ), throwsToolExit(message: 'Upgrade Flutter'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fileSystem,
+ ProcessManager: () => FakeProcessManager.any(),
+ Platform: () => windowsPlatform,
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+ });
+
testUsingContext('Windows build does not spew stdout to status logger', () async {
final BuildWindowsCommand command = BuildWindowsCommand()
..visualStudioOverride = mockVisualStudio;
@@ -135,11 +191,11 @@
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[
- r'C:\packages\flutter_tools\bin\vs_build.bat',
+ fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
vcvarsPath,
- globals.fs.path.basename(solutionPath),
+ fileSystem.path.basename(solutionPath),
'Release',
- ], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
+ ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess;
});
@@ -149,7 +205,7 @@
expect(testLogger.statusText, isNot(contains('STDOUT STUFF')));
expect(testLogger.traceText, contains('STDOUT STUFF'));
}, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
@@ -163,11 +219,11 @@
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[
- r'C:\packages\flutter_tools\bin\vs_build.bat',
+ fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
vcvarsPath,
- globals.fs.path.basename(solutionPath),
+ fileSystem.path.basename(solutionPath),
'Release',
- ], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
+ ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess;
});
@@ -176,14 +232,14 @@
);
// Spot-check important elements from the properties file.
- final File propsFile = globals.fs.file(r'C:\windows\flutter\ephemeral\Generated.props');
+ final File propsFile = fileSystem.file(r'C:\windows\flutter\ephemeral\Generated.props');
expect(propsFile.existsSync(), true);
final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync());
expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
expect(props.findAllElements('ItemGroup').length, 1);
- expect(props.findAllElements('FLUTTER_ROOT').first.text, r'C:\');
+ expect(props.findAllElements('FLUTTER_ROOT').first.text, flutterRoot);
}, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
@@ -197,11 +253,11 @@
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[
- r'C:\packages\flutter_tools\bin\vs_build.bat',
+ fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
vcvarsPath,
- globals.fs.path.basename(solutionPath),
+ fileSystem.path.basename(solutionPath),
'Release',
- ], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
+ ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess;
});
@@ -211,7 +267,7 @@
expect(testLogger.statusText, contains('🚧'));
}, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
+ FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
index a9db5b9..b08b65f 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:args/command_runner.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/doctor.dart';
@@ -23,6 +24,7 @@
setUp(() {
testbed = Testbed(setup: () {
+ Cache.flutterRoot = 'flutter';
final List<String> paths = <String>[
globals.fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'),
globals.fs.path.join('flutter', 'packages', 'flutter_driver', 'pubspec.yaml'),
@@ -34,6 +36,13 @@
for (final String path in paths) {
globals.fs.file(path).createSync(recursive: true);
}
+ // Set up enough of the packages to satisfy the templating code.
+ final File packagesFile = globals.fs.file(
+ globals.fs.path.join('flutter', 'packages', 'flutter_tools', '.packages'));
+ final Directory templateImagesDirectory = globals.fs.directory('flutter_template_images');
+ templateImagesDirectory.createSync(recursive: true);
+ packagesFile.createSync(recursive: true);
+ packagesFile.writeAsStringSync('flutter_template_images:file:///${templateImagesDirectory.uri}');
}, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
});
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index 88a5760..f85d12f 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -631,6 +631,66 @@
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false),
});
+ testUsingContext('app supports Windows if requested', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ expect(projectDir.childDirectory('windows').childFile('Runner.sln').existsSync(), true);
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+ });
+
+ testUsingContext('app does not include Windows by default', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ expect(projectDir.childDirectory('windows').childFile('Runner.sln').existsSync(), false);
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
+ });
+
+ testUsingContext('plugin supports Windows if requested', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+ expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), true);
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+ });
+
+ testUsingContext('plugin does not include Windows by default', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+ expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), false);
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
+ });
+
testUsingContext('plugin uses new platform schema', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
diff --git a/packages/flutter_tools/test/general.shard/license_collector_test.dart b/packages/flutter_tools/test/general.shard/license_collector_test.dart
index 7e886de..a41a9a4 100644
--- a/packages/flutter_tools/test/general.shard/license_collector_test.dart
+++ b/packages/flutter_tools/test/general.shard/license_collector_test.dart
@@ -261,7 +261,7 @@
'foo': Uri.parse('file:///foo/lib/'),
'bar': Uri.parse('file:///bar/lib/'),
'fizz': Uri.parse('file:///fizz/lib/'),
- });
+ }, fileSystem: fileSystem);
final LicenseResult result = licenseCollector.obtainLicenses(packageMap);
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart
index 2d8bfab..1647480 100644
--- a/packages/flutter_tools/test/general.shard/project_test.dart
+++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -636,6 +636,15 @@
.childDirectory('packages')
.childDirectory('flutter_tools')
.childDirectory('schema'), testFileSystem);
+ // Set up enough of the packages to satisfy the templating code.
+ final File packagesFile = testFileSystem.directory(Cache.flutterRoot)
+ .childDirectory('packages')
+ .childDirectory('flutter_tools')
+ .childFile('.packages');
+ final Directory dummyTemplateImagesDirectory = testFileSystem.directory(Cache.flutterRoot).parent;
+ dummyTemplateImagesDirectory.createSync(recursive: true);
+ packagesFile.createSync(recursive: true);
+ packagesFile.writeAsStringSync('flutter_template_images:${dummyTemplateImagesDirectory.uri}');
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory();
diff --git a/packages/flutter_tools/test/src/throwing_pub.dart b/packages/flutter_tools/test/src/throwing_pub.dart
index 849afd5..8243c73 100644
--- a/packages/flutter_tools/test/src/throwing_pub.dart
+++ b/packages/flutter_tools/test/src/throwing_pub.dart
@@ -16,7 +16,7 @@
bool retry,
bool showTraceForErrors,
}) {
- throw UnsupportedError('Attempted to inovke pub during test.');
+ throw UnsupportedError('Attempted to invoke pub during test.');
}
@override
@@ -29,11 +29,11 @@
bool checkLastModified = true,
bool skipPubspecYamlCheck = false,
}) {
- throw UnsupportedError('Attempted to inovke pub during test.');
+ throw UnsupportedError('Attempted to invoke pub during test.');
}
@override
Future<void> interactively(List<String> arguments, {String directory}) {
- throw UnsupportedError('Attempted to inovke pub during test.');
+ throw UnsupportedError('Attempted to invoke pub during test.');
}
}
diff --git a/packages/flutter_tools/test/template_test.dart b/packages/flutter_tools/test/template_test.dart
index 936cf57..c90bd91 100644
--- a/packages/flutter_tools/test/template_test.dart
+++ b/packages/flutter_tools/test/template_test.dart
@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/template.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
@@ -18,13 +20,69 @@
});
test('Template.render throws ToolExit when FileSystem exception is raised', () => testbed.run(() {
- final Template template = Template(globals.fs.directory('examples'), globals.fs.currentDirectory);
+ final Template template = Template(globals.fs.directory('examples'), globals.fs.currentDirectory, null, fileSystem: globals.fs);
final MockDirectory mockDirectory = MockDirectory();
when(mockDirectory.createSync(recursive: true)).thenThrow(const FileSystemException());
expect(() => template.render(mockDirectory, <String, Object>{}),
throwsToolExit());
}));
+
+ test('Template.render replaces .img.tmpl files with files from the image source', () => testbed.run(() {
+ final MemoryFileSystem fileSystem = MemoryFileSystem();
+ final Directory templateDir = fileSystem.directory('templates');
+ final Directory imageSourceDir = fileSystem.directory('template_images');
+ final Directory destination = fileSystem.directory('target');
+ const String imageName = 'some_image.png';
+ templateDir.childFile('$imageName.img.tmpl').createSync(recursive: true);
+ final File sourceImage = imageSourceDir.childFile(imageName);
+ sourceImage.createSync(recursive: true);
+ sourceImage.writeAsStringSync('Ceci n\'est pas une pipe');
+
+ final Template template = Template(templateDir, templateDir, imageSourceDir, fileSystem: fileSystem);
+ template.render(destination, <String, Object>{});
+
+ final File destinationImage = destination.childFile(imageName);
+ expect(destinationImage.existsSync(), true);
+ expect(destinationImage.readAsBytesSync(), equals(sourceImage.readAsBytesSync()));
+ }));
+
+ test('Template.fromName runs pub get if .packages is missing', () => testbed.run(() async {
+ final MemoryFileSystem fileSystem = MemoryFileSystem();
+
+ // Attempting to run pub in a test throws.
+ await expectLater(Template.fromName('app', fileSystem: fileSystem),
+ throwsUnsupportedError);
+ }));
+
+ test('Template.fromName runs pub get if .packages is missing flutter_template_images', () => testbed.run(() async {
+ final MemoryFileSystem fileSystem = MemoryFileSystem();
+ Cache.flutterRoot = '/flutter';
+ final File packagesFile = fileSystem.directory(Cache.flutterRoot)
+ .childDirectory('packages')
+ .childDirectory('flutter_tools')
+ .childFile('.packages');
+ packagesFile.createSync(recursive: true);
+
+ // Attempting to run pub in a test throws.
+ await expectLater(Template.fromName('app', fileSystem: fileSystem),
+ throwsUnsupportedError);
+ }));
+
+ test('Template.fromName runs pub get if flutter_template_images directory is missing', () => testbed.run(() async {
+ final MemoryFileSystem fileSystem = MemoryFileSystem();
+ Cache.flutterRoot = '/flutter';
+ final File packagesFile = fileSystem.directory(Cache.flutterRoot)
+ .childDirectory('packages')
+ .childDirectory('flutter_tools')
+ .childFile('.packages');
+ packagesFile.createSync(recursive: true);
+ packagesFile.writeAsStringSync('flutter_template_images:file:///flutter_template_images');
+
+ // Attempting to run pub in a test throws.
+ await expectLater(Template.fromName('app', fileSystem: fileSystem),
+ throwsUnsupportedError);
+ }));
}
class MockDirectory extends Mock implements Directory {}