Unverified Commit 685e9d1e authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

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
parent 58cad787
...@@ -15,8 +15,17 @@ ...@@ -15,8 +15,17 @@
*.yaml text *.yaml text
# Make sure that these Windows files always have CRLF line endings in checkout # Make sure that these Windows files always have CRLF line endings in checkout
*.bat text eol=crlf *.bat text eol=crlf
*.ps1 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 # Never perform LF normalization on these files
*.ico binary *.ico binary
......
...@@ -603,6 +603,7 @@ Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche ...@@ -603,6 +603,7 @@ Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche
.where((File file) => path.extension(file.path) != '.snapshot') .where((File file) => path.extension(file.path) != '.snapshot')
.where((File file) => path.extension(file.path) != '.png') .where((File file) => path.extension(file.path) != '.png')
.where((File file) => path.extension(file.path) != '.jpg') .where((File file) => path.extension(file.path) != '.jpg')
.where((File file) => path.extension(file.path) != '.ico')
.where((File file) => path.extension(file.path) != '.jar') .where((File file) => path.extension(file.path) != '.jar')
.toList(); .toList();
final List<String> problems = <String>[]; final List<String> problems = <String>[];
...@@ -685,7 +686,9 @@ class Hash256 { ...@@ -685,7 +686,9 @@ class Hash256 {
// DO NOT ADD ANY ENTRIES TO THIS LIST. // DO NOT ADD ANY ENTRIES TO THIS LIST.
// We have a policy of not checking in binaries into this repository. // 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>{ final Set<Hash256> _grandfatheredBinaries = <Hash256>{
// DEFAULT ICON IMAGES // DEFAULT ICON IMAGES
...@@ -1044,7 +1047,9 @@ final Set<Hash256> _grandfatheredBinaries = <Hash256>{ ...@@ -1044,7 +1047,9 @@ final Set<Hash256> _grandfatheredBinaries = <Hash256>{
Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256> grandfatheredBinaries }) async { Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256> grandfatheredBinaries }) async {
// Please do not add anything to the _grandfatheredBinaries set above. // Please do not add anything to the _grandfatheredBinaries set above.
// We have a policy of not checking in binaries into this repository. // 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( assert(
_grandfatheredBinaries _grandfatheredBinaries
.expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d]) .expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d])
......
...@@ -148,7 +148,7 @@ class _ManifestAssetBundle implements AssetBundle { ...@@ -148,7 +148,7 @@ class _ManifestAssetBundle implements AssetBundle {
final String assetBasePath = globals.fs.path.dirname(globals.fs.path.absolute(manifestPath)); 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>[]; final List<Uri> wildcardDirectories = <Uri>[];
// The _assetVariants map contains an entry for each asset listed // The _assetVariants map contains an entry for each asset listed
......
...@@ -407,6 +407,7 @@ class CreateCommand extends FlutterCommand { ...@@ -407,6 +407,7 @@ class CreateCommand extends FlutterCommand {
web: featureFlags.isWebEnabled, web: featureFlags.isWebEnabled,
linux: featureFlags.isLinuxEnabled, linux: featureFlags.isLinuxEnabled,
macos: featureFlags.isMacOSEnabled, macos: featureFlags.isMacOSEnabled,
windows: featureFlags.isWindowsEnabled,
); );
final String relativeDirPath = globals.fs.path.relative(projectDirPath); final String relativeDirPath = globals.fs.path.relative(projectDirPath);
...@@ -507,6 +508,12 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -507,6 +508,12 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'You will likely need to re-create the "linux" directory after future ' 'You will likely need to re-create the "linux" directory after future '
'Flutter updates.'); '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(); return FlutterCommandResult.success();
} }
...@@ -517,7 +524,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -517,7 +524,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description') ? stringArg('description')
: 'A new flutter module project.'; : 'A new flutter module project.';
templateContext['description'] = description; 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')) { if (boolArg('pub')) {
await pub.get( await pub.get(
context: PubContext.create, context: PubContext.create,
...@@ -536,7 +543,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -536,7 +543,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description') ? stringArg('description')
: 'A new Flutter package project.'; : 'A new Flutter package project.';
templateContext['description'] = description; templateContext['description'] = description;
generatedCount += _renderTemplate('package', directory, templateContext, overwrite: overwrite); generatedCount += await _renderTemplate('package', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) { if (boolArg('pub')) {
await pub.get( await pub.get(
context: PubContext.createPackage, context: PubContext.createPackage,
...@@ -553,7 +560,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -553,7 +560,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description') ? stringArg('description')
: 'A new flutter plugin project.'; : 'A new flutter plugin project.';
templateContext['description'] = description; templateContext['description'] = description;
generatedCount += _renderTemplate('plugin', directory, templateContext, overwrite: overwrite); generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) { if (boolArg('pub')) {
await pub.get( await pub.get(
context: PubContext.createPlugin, context: PubContext.createPlugin,
...@@ -581,13 +588,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -581,13 +588,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async { Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async {
int generatedCount = 0; int generatedCount = 0;
generatedCount += _renderTemplate('app', directory, templateContext, overwrite: overwrite); generatedCount += await _renderTemplate('app', directory, templateContext, overwrite: overwrite);
final FlutterProject project = FlutterProject.fromDirectory(directory); final FlutterProject project = FlutterProject.fromDirectory(directory);
generatedCount += _injectGradleWrapper(project); generatedCount += _injectGradleWrapper(project);
if (boolArg('with-driver-test')) { if (boolArg('with-driver-test')) {
final Directory testDirectory = directory.childDirectory('test_driver'); 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')) { if (boolArg('pub')) {
...@@ -626,6 +633,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -626,6 +633,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
bool web = false, bool web = false,
bool linux = false, bool linux = false,
bool macos = false, bool macos = false,
bool windows = false,
}) { }) {
flutterRoot = globals.fs.path.normalize(flutterRoot); flutterRoot = globals.fs.path.normalize(flutterRoot);
...@@ -651,6 +659,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -651,6 +659,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'pluginClass': pluginClass, 'pluginClass': pluginClass,
'pluginDartClass': pluginDartClass, 'pluginDartClass': pluginDartClass,
'pluginCppHeaderGuard': projectName.toUpperCase(), 'pluginCppHeaderGuard': projectName.toUpperCase(),
'pluginProjectUUID': Uuid().generateV4().toUpperCase(),
'withPluginHook': withPluginHook, 'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage, 'androidLanguage': androidLanguage,
'iosLanguage': iosLanguage, 'iosLanguage': iosLanguage,
...@@ -659,12 +668,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi ...@@ -659,12 +668,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'web': web, 'web': web,
'linux': linux, 'linux': linux,
'macos': macos, 'macos': macos,
'windows': windows,
'year': DateTime.now().year, 'year': DateTime.now().year,
}; };
} }
int _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) { Future<int> _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) async {
final Template template = Template.fromName(templateName); final Template template = await Template.fromName(templateName, fileSystem: globals.fs);
return template.render(directory, context, overwriteExisting: overwrite); return template.render(directory, context, overwriteExisting: overwrite);
} }
......
...@@ -247,7 +247,7 @@ class IdeConfigCommand extends FlutterCommand { ...@@ -247,7 +247,7 @@ class IdeConfigCommand extends FlutterCommand {
} }
int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) { 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( return template.render(
globals.fs.directory(dirPath), globals.fs.directory(dirPath),
context, context,
......
...@@ -196,7 +196,7 @@ class StdoutHandler { ...@@ -196,7 +196,7 @@ class StdoutHandler {
/// Converts filesystem paths to package URIs. /// Converts filesystem paths to package URIs.
class PackageUriMapper { class PackageUriMapper {
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) { 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 bool isWindowsPath = globals.platform.isWindows && !scriptPath.startsWith('org-dartlang-app');
final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString(); final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString();
for (final String packageName in packageMap.keys) { for (final String packageName in packageMap.keys) {
......
...@@ -2,27 +2,35 @@ ...@@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:meta/meta.dart';
// TODO(bkonyi): remove deprecated member usage, https://github.com/flutter/flutter/issues/51951 // TODO(bkonyi): remove deprecated member usage, https://github.com/flutter/flutter/issues/51951
// ignore: deprecated_member_use // ignore: deprecated_member_use
import 'package:package_config/packages_file.dart' as packages_file; 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'; const String kPackagesFileName = '.packages';
Map<String, Uri> _parse(String packagesPath) { Map<String, Uri> _parse(String packagesPath, FileSystem fileSystem) {
final List<int> source = globals.fs.file(packagesPath).readAsBytesSync(); final List<int> source = fileSystem.file(packagesPath).readAsBytesSync();
return packages_file.parse(source, return packages_file.parse(source,
Uri.file(packagesPath, windows: globals.platform.isWindows)); Uri.file(packagesPath, windows: globals.platform.isWindows));
} }
class PackageMap { class PackageMap {
PackageMap(this.packagesPath); PackageMap(this.packagesPath, {
@required FileSystem fileSystem,
}) : _fileSystem = fileSystem;
/// Create a [PackageMap] for testing. /// Create a [PackageMap] for testing.
PackageMap.test(Map<String, Uri> input) PackageMap.test(Map<String, Uri> input, {
: packagesPath = '.packages', @required FileSystem fileSystem,
_map = input; }) : packagesPath = '.packages',
_map = input,
_fileSystem = fileSystem;
final FileSystem _fileSystem;
static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName; static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;
...@@ -38,7 +46,7 @@ class PackageMap { ...@@ -38,7 +46,7 @@ class PackageMap {
/// Load and parses the .packages file. /// Load and parses the .packages file.
void load() { void load() {
_map ??= _parse(packagesPath); _map ??= _parse(packagesPath, _fileSystem);
} }
Map<String, Uri> get map { Map<String, Uri> get map {
...@@ -59,17 +67,17 @@ class PackageMap { ...@@ -59,17 +67,17 @@ class PackageMap {
if (packageBase == null) { if (packageBase == null) {
return null; return null;
} }
final String packageRelativePath = globals.fs.path.joinAll(pathSegments); final String packageRelativePath = _fileSystem.path.joinAll(pathSegments);
return packageBase.resolveUri(globals.fs.path.toUri(packageRelativePath)); return packageBase.resolveUri(_fileSystem.path.toUri(packageRelativePath));
} }
String checkValid() { String checkValid() {
if (globals.fs.isFileSync(packagesPath)) { if (_fileSystem.isFileSync(packagesPath)) {
return null; return null;
} }
String message = '$packagesPath does not exist.'; String message = '$packagesPath does not exist.';
final String pubspecPath = globals.fs.path.absolute(globals.fs.path.dirname(packagesPath), 'pubspec.yaml'); final String pubspecPath = _fileSystem.path.absolute(_fileSystem.path.dirname(packagesPath), 'pubspec.yaml');
if (globals.fs.isFileSync(pubspecPath)) { if (_fileSystem.isFileSync(pubspecPath)) {
message += '\nDid you run "flutter pub get" in this directory?'; message += '\nDid you run "flutter pub get" in this directory?';
} else { } else {
message += '\nDid you run this command from the same directory as your pubspec.yaml file?'; message += '\nDid you run this command from the same directory as your pubspec.yaml file?';
......
...@@ -307,7 +307,7 @@ List<Plugin> findPlugins(FlutterProject project) { ...@@ -307,7 +307,7 @@ List<Plugin> findPlugins(FlutterProject project) {
project.directory.path, project.directory.path,
PackageMap.globalPackagesPath, PackageMap.globalPackagesPath,
); );
packages = PackageMap(packagesFile).map; packages = PackageMap(packagesFile, fileSystem: globals.fs).map;
} on FormatException catch (e) { } on FormatException catch (e) {
globals.printTrace('Invalid .packages file: $e'); globals.printTrace('Invalid .packages file: $e');
return plugins; return plugins;
......
...@@ -460,7 +460,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -460,7 +460,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
Map<String, String> _buildSettings; Map<String, String> _buildSettings;
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
_regenerateFromTemplateIfNeeded(); await _regenerateFromTemplateIfNeeded();
if (!_flutterLibRoot.existsSync()) { if (!_flutterLibRoot.existsSync()) {
return; return;
} }
...@@ -477,7 +477,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -477,7 +477,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
} }
} }
void _regenerateFromTemplateIfNeeded() { Future<void> _regenerateFromTemplateIfNeeded() async {
if (!isModule) { if (!isModule) {
return; return;
} }
...@@ -491,18 +491,18 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -491,18 +491,18 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
} }
_deleteIfExistsSync(ephemeralDirectory); _deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'), globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory, ephemeralDirectory,
); );
// Add ephemeral host app, if a editable host app does not already exist. // Add ephemeral host app, if a editable host app does not already exist.
if (!_editableDirectory.existsSync()) { if (!_editableDirectory.existsSync()) {
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'), globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
ephemeralDirectory, ephemeralDirectory,
); );
if (hasPlugins(parent)) { if (hasPlugins(parent)) {
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
ephemeralDirectory, ephemeralDirectory,
); );
...@@ -542,19 +542,19 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -542,19 +542,19 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.'); throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
} }
_deleteIfExistsSync(ephemeralDirectory); _deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'), globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory, ephemeralDirectory,
); );
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'), globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
_editableDirectory, _editableDirectory,
); );
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
_editableDirectory, _editableDirectory,
); );
_overwriteFromTemplate( await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'), globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
_editableDirectory, _editableDirectory,
); );
...@@ -579,8 +579,8 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -579,8 +579,8 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
: hostAppRoot.childDirectory(_hostAppBundleName); : hostAppRoot.childDirectory(_hostAppBundleName);
} }
void _overwriteFromTemplate(String path, Directory target) { Future<void> _overwriteFromTemplate(String path, Directory target) async {
final Template template = Template.fromName(path); final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render( template.render(
target, target,
<String, dynamic>{ <String, dynamic>{
...@@ -679,11 +679,11 @@ class AndroidProject extends FlutterProjectPlatform { ...@@ -679,11 +679,11 @@ class AndroidProject extends FlutterProjectPlatform {
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
if (isModule && _shouldRegenerateFromTemplate()) { if (isModule && _shouldRegenerateFromTemplate()) {
_regenerateLibrary(); await _regenerateLibrary();
// Add ephemeral host app, if an editable host app does not already exist. // Add ephemeral host app, if an editable host app does not already exist.
if (!_editableHostAppDirectory.existsSync()) { if (!_editableHostAppDirectory.existsSync()) {
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory); await _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_ephemeral'), ephemeralDirectory);
} }
} }
if (!hostAppGradleRoot.existsSync()) { if (!hostAppGradleRoot.existsSync()) {
...@@ -704,10 +704,10 @@ class AndroidProject extends FlutterProjectPlatform { ...@@ -704,10 +704,10 @@ class AndroidProject extends FlutterProjectPlatform {
if (_editableHostAppDirectory.existsSync()) { if (_editableHostAppDirectory.existsSync()) {
throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.'); throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
} }
_regenerateLibrary(); await _regenerateLibrary();
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory); await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory); await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory); await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory); gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties')); gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
await injectPlugins(parent); await injectPlugins(parent);
...@@ -717,19 +717,19 @@ class AndroidProject extends FlutterProjectPlatform { ...@@ -717,19 +717,19 @@ class AndroidProject extends FlutterProjectPlatform {
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app'); Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
void _regenerateLibrary() { Future<void> _regenerateLibrary() async {
_deleteIfExistsSync(ephemeralDirectory); _deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join( await _overwriteFromTemplate(globals.fs.path.join(
'module', 'module',
'android', 'android',
featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library', featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
), ephemeralDirectory); ), ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory); await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory); gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
} }
void _overwriteFromTemplate(String path, Directory target) { Future<void> _overwriteFromTemplate(String path, Directory target) async {
final Template template = Template.fromName(path); final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render( template.render(
target, target,
<String, dynamic>{ <String, dynamic>{
......
...@@ -803,7 +803,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -803,7 +803,7 @@ abstract class FlutterCommand extends Command<void> {
// Validate the current package map only if we will not be running "pub get" later. // Validate the current package map only if we will not be running "pub get" later.
if (parent?.name != 'pub' && !(_usesPubOption && boolArg('pub'))) { 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) { if (error != null) {
throw ToolExit(error); throw ToolExit(error);
} }
......
...@@ -354,7 +354,7 @@ class FlutterCommandRunner extends CommandRunner<void> { ...@@ -354,7 +354,7 @@ class FlutterCommandRunner extends CommandRunner<void> {
if (engineSourcePath == null && globalResults['local-engine'] != null) { if (engineSourcePath == null && globalResults['local-engine'] != null) {
try { 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. // 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)) { if (engineUri != null && globals.fs.identicalSync(globals.fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'pkg', kFlutterEnginePackageName, 'lib'), engineUri.path)) {
engineUri = null; engineUri = null;
...@@ -491,7 +491,7 @@ class FlutterCommandRunner extends CommandRunner<void> { ...@@ -491,7 +491,7 @@ class FlutterCommandRunner extends CommandRunner<void> {
// Check that the flutter running is that same as the one referenced in the pubspec. // Check that the flutter running is that same as the one referenced in the pubspec.
if (globals.fs.isFileSync(kPackagesFileName)) { if (globals.fs.isFileSync(kPackagesFileName)) {
final PackageMap packageMap = PackageMap(kPackagesFileName); final PackageMap packageMap = PackageMap(kPackagesFileName, fileSystem: globals.fs);
Uri flutterUri; Uri flutterUri;
try { try {
flutterUri = packageMap.map['flutter']; flutterUri = packageMap.map['flutter'];
......
...@@ -2,27 +2,35 @@ ...@@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:meta/meta.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'cache.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 /// 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 /// 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 /// as is, the directory itself can end with '.tmpl' extension. Files within
/// such a directory may also contain the '.tmpl' extension and will be /// such a directory may also contain the '.tmpl' or '.img.tmpl' extensions and
/// considered for expansion. In case certain files need to be copied but /// will be considered for expansion. In case certain files need to be copied
/// without template expansion (images, data files, etc.), the '.copy.tmpl' /// but without template expansion (data files, etc.), the '.copy.tmpl'
/// extension may be used. /// extension may be used.
/// ///
/// Folders with platform/language-specific content must be named /// Folders with platform/language-specific content must be named
/// '<platform>-<language>.tmpl'. /// '<platform>-<language>.tmpl'.
/// ///
/// Files in the destination will contain none of the '.tmpl', '.copy.tmpl' /// Files in the destination will contain none of the '.tmpl', '.copy.tmpl',
/// or '-<language>.tmpl' extensions. /// 'img.tmpl', or '-<language>.tmpl' extensions.
class Template { class Template {
Template(Directory templateSource, Directory baseDir) { Template(Directory templateSource, Directory baseDir, this.imageSourceDir, {
@required FileSystem fileSystem,
}) : _fileSystem = fileSystem {
_templateFilePaths = <String, String>{}; _templateFilePaths = <String, String>{};
if (!templateSource.existsSync()) { if (!templateSource.existsSync()) {
...@@ -37,27 +45,31 @@ class Template { ...@@ -37,27 +45,31 @@ class Template {
continue; continue;
} }
final String relativePath = globals.fs.path.relative(entity.path, final String relativePath = fileSystem.path.relative(entity.path,
from: baseDir.absolute.path); from: baseDir.absolute.path);
if (relativePath.contains(templateExtension)) { if (relativePath.contains(templateExtension)) {
// If '.tmpl' appears anywhere within the path of this entity, it is // If '.tmpl' appears anywhere within the path of this entity, it is
// is a candidate for rendering. This catches cases where the folder // is a candidate for rendering. This catches cases where the folder
// itself is a template. // 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 // All named templates are placed in the 'templates' directory
final Directory templateDir = templateDirectoryInPackage(name); final Directory templateDir = _templateDirectoryInPackage(name, fileSystem);
return Template(templateDir, templateDir); 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 templateExtension = '.tmpl';
static const String copyTemplateExtension = '.copy.tmpl'; static const String copyTemplateExtension = '.copy.tmpl';
static const String imageTemplateExtension = '.img.tmpl';
final Pattern _kTemplateLanguageVariant = RegExp(r'(\w+)-(\w+)\.tmpl.*'); final Pattern _kTemplateLanguageVariant = RegExp(r'(\w+)-(\w+)\.tmpl.*');
final Directory imageSourceDir;
Map<String /* relative */, String /* absolute source */> _templateFilePaths; Map<String /* relative */, String /* absolute source */> _templateFilePaths;
...@@ -109,14 +121,20 @@ class Template { ...@@ -109,14 +121,20 @@ class Template {
if (relativeDestinationPath.startsWith('macos.tmpl') && !macOS) { if (relativeDestinationPath.startsWith('macos.tmpl') && !macOS) {
return null; 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 projectName = context['projectName'] as String;
final String androidIdentifier = context['androidIdentifier'] as String; final String androidIdentifier = context['androidIdentifier'] as String;
final String pluginClass = context['pluginClass'] as String; final String pluginClass = context['pluginClass'] as String;
final String destinationDirPath = destination.absolute.path; final String destinationDirPath = destination.absolute.path;
final String pathSeparator = globals.fs.path.separator; final String pathSeparator = _fileSystem.path.separator;
String finalDestinationPath = globals.fs.path String finalDestinationPath = _fileSystem.path
.join(destinationDirPath, relativeDestinationPath) .join(destinationDirPath, relativeDestinationPath)
.replaceAll(copyTemplateExtension, '') .replaceAll(copyTemplateExtension, '')
.replaceAll(imageTemplateExtension, '')
.replaceAll(templateExtension, ''); .replaceAll(templateExtension, '');
if (androidIdentifier != null) { if (androidIdentifier != null) {
...@@ -142,8 +160,8 @@ class Template { ...@@ -142,8 +160,8 @@ class Template {
if (finalDestinationPath == null) { if (finalDestinationPath == null) {
return; return;
} }
final File finalDestinationFile = globals.fs.file(finalDestinationPath); final File finalDestinationFile = _fileSystem.file(finalDestinationPath);
final String relativePathForLogging = globals.fs.path.relative(finalDestinationFile.path); final String relativePathForLogging = _fileSystem.path.relative(finalDestinationFile.path);
// Step 1: Check if the file needs to be overwritten. // Step 1: Check if the file needs to be overwritten.
...@@ -169,7 +187,7 @@ class Template { ...@@ -169,7 +187,7 @@ class Template {
fileCount++; fileCount++;
finalDestinationFile.createSync(recursive: true); 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 // Step 2: If the absolute paths ends with a '.copy.tmpl', this file does
// not need mustache rendering but needs to be directly copied. // not need mustache rendering but needs to be directly copied.
...@@ -180,7 +198,18 @@ class Template { ...@@ -180,7 +198,18 @@ class Template {
return; 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. // rendering via mustache.
if (sourceFile.path.endsWith(templateExtension)) { if (sourceFile.path.endsWith(templateExtension)) {
...@@ -192,7 +221,7 @@ class Template { ...@@ -192,7 +221,7 @@ class Template {
return; 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. // does. Directly copy the file to the destination.
sourceFile.copySync(finalDestinationFile.path); sourceFile.copySync(finalDestinationFile.path);
...@@ -202,8 +231,39 @@ class Template { ...@@ -202,8 +231,39 @@ class Template {
} }
} }
Directory templateDirectoryInPackage(String name) { Directory _templateDirectoryInPackage(String name, FileSystem fileSystem) {
final String templatesDir = globals.fs.path.join(Cache.flutterRoot, final String templatesDir = fileSystem.path.join(Cache.flutterRoot,
'packages', 'flutter_tools', 'templates'); '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,
);
} }
...@@ -111,7 +111,7 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -111,7 +111,7 @@ class FlutterWebPlatform extends PlatformPlugin {
'packages', 'packages',
'flutter_tools', 'flutter_tools',
'.packages', '.packages',
)); ), fileSystem: globals.fs);
/// Uri of the test package. /// Uri of the test package.
Uri get testUri => _flutterToolsPackageMap.map['test']; Uri get testUri => _flutterToolsPackageMap.map['test'];
......
...@@ -27,6 +27,20 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { ...@@ -27,6 +27,20 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
'to learn about adding Windows support to a project.'); '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. // Ensure that necessary emphemeral files are generated and up to date.
_writeGeneratedFlutterProperties(windowsProject, buildInfo, target); _writeGeneratedFlutterProperties(windowsProject, buildInfo, target);
createPluginSymlinks(windowsProject.project); createPluginSymlinks(windowsProject.project);
...@@ -109,3 +123,25 @@ void _writeGeneratedFlutterProperties(WindowsProject windowsProject, BuildInfo b ...@@ -109,3 +123,25 @@ void _writeGeneratedFlutterProperties(WindowsProject windowsProject, BuildInfo b
propsFile.createSync(recursive: true); propsFile.createSync(recursive: true);
propsFile.writeAsStringSync(PropertySheet(environmentVariables: environment).toString()); 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);
}
...@@ -16,6 +16,7 @@ dependencies: ...@@ -16,6 +16,7 @@ dependencies:
coverage: 0.13.9 coverage: 0.13.9
crypto: 2.1.3 crypto: 2.1.3
file: 5.1.0 file: 5.1.0
flutter_template_images: 1.0.0
http: 0.12.0+4 http: 0.12.0+4
intl: 0.16.1 intl: 0.16.1
json_rpc_2: 2.1.0 json_rpc_2: 2.1.0
...@@ -111,4 +112,4 @@ dartdoc: ...@@ -111,4 +112,4 @@ dartdoc:
# Exclude this package from the hosted API docs. # Exclude this package from the hosted API docs.
nodoc: true nodoc: true
# PUBSPEC CHECKSUM: ce97 # PUBSPEC CHECKSUM: 8b7f
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/
<?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>
<?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>
// 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

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
<?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>
#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;
}
//{{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
<?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>
@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%
)
@echo off
:: Run flutter tool backend.
set BUILD_MODE=%~1
"%FLUTTER_ROOT%\packages\flutter_tools\bin\tool_backend" windows-x64 %BUILD_MODE%
#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_; }
#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_
#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;
#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_
#include "sample_plugin.h" #include "{{projectName}}_plugin.h"
#include <flutter/method_channel.h> #include <flutter/method_channel.h>
#include <flutter/plugin_registrar_glfw.h> #include <flutter/plugin_registrar_glfw.h>
......
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/
<?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>
<?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>
#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());
}
#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_
...@@ -15,13 +15,13 @@ import 'package:flutter_tools/src/windows/visual_studio.dart'; ...@@ -15,13 +15,13 @@ import 'package:flutter_tools/src/windows/visual_studio.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.dart'; import '../../src/mocks.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
const String flutterRoot = r'C:\flutter';
const String solutionPath = r'C:\windows\Runner.sln'; const String solutionPath = r'C:\windows\Runner.sln';
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'; const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
...@@ -30,17 +30,19 @@ final Platform windowsPlatform = FakePlatform( ...@@ -30,17 +30,19 @@ final Platform windowsPlatform = FakePlatform(
operatingSystem: 'windows', operatingSystem: 'windows',
environment: <String, String>{ environment: <String, String>{
'PROGRAMFILES(X86)': r'C:\Program Files (x86)\', 'PROGRAMFILES(X86)': r'C:\Program Files (x86)\',
'FLUTTER_ROOT': r'C:\', 'FLUTTER_ROOT': flutterRoot,
} }
); );
final Platform notWindowsPlatform = FakePlatform( final Platform notWindowsPlatform = FakePlatform(
operatingSystem: 'linux', operatingSystem: 'linux',
environment: <String, String>{ environment: <String, String>{
'FLUTTER_ROOT': r'C:\', 'FLUTTER_ROOT': flutterRoot,
} }
); );
void main() { void main() {
FileSystem fileSystem;
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
MockProcess mockProcess; MockProcess mockProcess;
MockVisualStudio mockVisualStudio; MockVisualStudio mockVisualStudio;
...@@ -50,6 +52,8 @@ void main() { ...@@ -50,6 +52,8 @@ void main() {
}); });
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
Cache.flutterRoot = flutterRoot;
mockProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager();
mockProcess = MockProcess(); mockProcess = MockProcess();
mockVisualStudio = MockVisualStudio(); mockVisualStudio = MockVisualStudio();
...@@ -66,15 +70,35 @@ void main() { ...@@ -66,15 +70,35 @@ void main() {
// Creates the mock files necessary to look like a Flutter project. // Creates the mock files necessary to look like a Flutter project.
void setUpMockCoreProjectFiles() { void setUpMockCoreProjectFiles() {
globals.fs.file('pubspec.yaml').createSync(); fileSystem.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync(); fileSystem.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
} }
// Creates the mock files necessary to run a build. // Creates the mock files necessary to run a build.
void setUpMockProjectFilesForBuild() { void setUpMockProjectFilesForBuild({int templateVersion}) {
globals.fs.file(solutionPath).createSync(recursive: true); fileSystem.file(solutionPath).createSync(recursive: true);
setUpMockCoreProjectFiles(); 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 { testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
...@@ -88,7 +112,7 @@ void main() { ...@@ -88,7 +112,7 @@ void main() {
), throwsToolExit()); ), throwsToolExit());
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -105,7 +129,7 @@ void main() { ...@@ -105,7 +129,7 @@ void main() {
), throwsToolExit(message: 'No Windows desktop project configured')); ), throwsToolExit(message: 'No Windows desktop project configured'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -122,8 +146,40 @@ void main() { ...@@ -122,8 +146,40 @@ void main() {
), throwsToolExit()); ), throwsToolExit());
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => notWindowsPlatform, 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(), 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), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -135,11 +191,11 @@ void main() { ...@@ -135,11 +191,11 @@ void main() {
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[ 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, vcvarsPath,
globals.fs.path.basename(solutionPath), fileSystem.path.basename(solutionPath),
'Release', 'Release',
], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async { ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess; return mockProcess;
}); });
...@@ -149,7 +205,7 @@ void main() { ...@@ -149,7 +205,7 @@ void main() {
expect(testLogger.statusText, isNot(contains('STDOUT STUFF'))); expect(testLogger.statusText, isNot(contains('STDOUT STUFF')));
expect(testLogger.traceText, contains('STDOUT STUFF')); expect(testLogger.traceText, contains('STDOUT STUFF'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
...@@ -163,11 +219,11 @@ void main() { ...@@ -163,11 +219,11 @@ void main() {
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[ 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, vcvarsPath,
globals.fs.path.basename(solutionPath), fileSystem.path.basename(solutionPath),
'Release', 'Release',
], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async { ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess; return mockProcess;
}); });
...@@ -176,14 +232,14 @@ void main() { ...@@ -176,14 +232,14 @@ void main() {
); );
// Spot-check important elements from the properties file. // 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); expect(propsFile.existsSync(), true);
final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync()); final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync());
expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros'); expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
expect(props.findAllElements('ItemGroup').length, 1); 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>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
...@@ -197,11 +253,11 @@ void main() { ...@@ -197,11 +253,11 @@ void main() {
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
when(mockProcessManager.start(<String>[ 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, vcvarsPath,
globals.fs.path.basename(solutionPath), fileSystem.path.basename(solutionPath),
'Release', 'Release',
], workingDirectory: globals.fs.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async { ], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
return mockProcess; return mockProcess;
}); });
...@@ -211,7 +267,7 @@ void main() { ...@@ -211,7 +267,7 @@ void main() {
expect(testLogger.statusText, contains('🚧')); expect(testLogger.statusText, contains('🚧'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:args/command_runner.dart'; 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/cache.dart';
import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
...@@ -23,6 +24,7 @@ void main() { ...@@ -23,6 +24,7 @@ void main() {
setUp(() { setUp(() {
testbed = Testbed(setup: () { testbed = Testbed(setup: () {
Cache.flutterRoot = 'flutter';
final List<String> paths = <String>[ final List<String> paths = <String>[
globals.fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'), globals.fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'),
globals.fs.path.join('flutter', 'packages', 'flutter_driver', 'pubspec.yaml'), globals.fs.path.join('flutter', 'packages', 'flutter_driver', 'pubspec.yaml'),
...@@ -34,6 +36,13 @@ void main() { ...@@ -34,6 +36,13 @@ void main() {
for (final String path in paths) { for (final String path in paths) {
globals.fs.file(path).createSync(recursive: true); 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>{ }, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(), DoctorValidatorsProvider: () => FakeDoctorValidatorsProvider(),
}); });
......
...@@ -631,6 +631,66 @@ void main() { ...@@ -631,6 +631,66 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false), 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 { testUsingContext('plugin uses new platform schema', () async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
......
...@@ -261,7 +261,7 @@ void main() { ...@@ -261,7 +261,7 @@ void main() {
'foo': Uri.parse('file:///foo/lib/'), 'foo': Uri.parse('file:///foo/lib/'),
'bar': Uri.parse('file:///bar/lib/'), 'bar': Uri.parse('file:///bar/lib/'),
'fizz': Uri.parse('file:///fizz/lib/'), 'fizz': Uri.parse('file:///fizz/lib/'),
}); }, fileSystem: fileSystem);
final LicenseResult result = licenseCollector.obtainLicenses(packageMap); final LicenseResult result = licenseCollector.obtainLicenses(packageMap);
......
...@@ -636,6 +636,15 @@ void testInMemory(String description, Future<void> testMethod()) { ...@@ -636,6 +636,15 @@ void testInMemory(String description, Future<void> testMethod()) {
.childDirectory('packages') .childDirectory('packages')
.childDirectory('flutter_tools') .childDirectory('flutter_tools')
.childDirectory('schema'), testFileSystem); .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(); final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory();
......
...@@ -16,7 +16,7 @@ class ThrowingPub implements Pub { ...@@ -16,7 +16,7 @@ class ThrowingPub implements Pub {
bool retry, bool retry,
bool showTraceForErrors, bool showTraceForErrors,
}) { }) {
throw UnsupportedError('Attempted to inovke pub during test.'); throw UnsupportedError('Attempted to invoke pub during test.');
} }
@override @override
...@@ -29,11 +29,11 @@ class ThrowingPub implements Pub { ...@@ -29,11 +29,11 @@ class ThrowingPub implements Pub {
bool checkLastModified = true, bool checkLastModified = true,
bool skipPubspecYamlCheck = false, bool skipPubspecYamlCheck = false,
}) { }) {
throw UnsupportedError('Attempted to inovke pub during test.'); throw UnsupportedError('Attempted to invoke pub during test.');
} }
@override @override
Future<void> interactively(List<String> arguments, {String directory}) { Future<void> interactively(List<String> arguments, {String directory}) {
throw UnsupportedError('Attempted to inovke pub during test.'); throw UnsupportedError('Attempted to invoke pub during test.');
} }
} }
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.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/template.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
...@@ -18,13 +20,69 @@ void main() { ...@@ -18,13 +20,69 @@ void main() {
}); });
test('Template.render throws ToolExit when FileSystem exception is raised', () => testbed.run(() { 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(); final MockDirectory mockDirectory = MockDirectory();
when(mockDirectory.createSync(recursive: true)).thenThrow(const FileSystemException()); when(mockDirectory.createSync(recursive: true)).thenThrow(const FileSystemException());
expect(() => template.render(mockDirectory, <String, Object>{}), expect(() => template.render(mockDirectory, <String, Object>{}),
throwsToolExit()); 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 {} class MockDirectory extends Mock implements Directory {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment