Skip to content

Commit 685e9d1

Browse files
Add pre-stable support for create on Windows (flutter#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 flutter#30704
1 parent 58cad78 commit 685e9d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1847
-110
lines changed

.gitattributes

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@
1515
*.yaml text
1616

1717
# Make sure that these Windows files always have CRLF line endings in checkout
18-
*.bat text eol=crlf
19-
*.ps1 text eol=crlf
18+
*.bat text eol=crlf
19+
*.ps1 text eol=crlf
20+
*.rc text eol=crlf
21+
*.sln text eol=crlf
22+
*.props text eol=crlf
23+
*.vcxproj text eol=crlf
24+
*.vcxproj.filters text eol=crlf
25+
# Including templatized versions.
26+
*.sln.tmpl text eol=crlf
27+
*.props.tmpl text eol=crlf
28+
*.vcxproj.tmpl text eol=crlf
2029

2130
# Never perform LF normalization on these files
2231
*.ico binary

dev/bots/analyze.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatche
603603
.where((File file) => path.extension(file.path) != '.snapshot')
604604
.where((File file) => path.extension(file.path) != '.png')
605605
.where((File file) => path.extension(file.path) != '.jpg')
606+
.where((File file) => path.extension(file.path) != '.ico')
606607
.where((File file) => path.extension(file.path) != '.jar')
607608
.toList();
608609
final List<String> problems = <String>[];
@@ -685,7 +686,9 @@ class Hash256 {
685686

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

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

packages/flutter_tools/lib/src/asset.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class _ManifestAssetBundle implements AssetBundle {
148148

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

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

154154
// The _assetVariants map contains an entry for each asset listed

packages/flutter_tools/lib/src/commands/create.dart

+17-7
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ class CreateCommand extends FlutterCommand {
407407
web: featureFlags.isWebEnabled,
408408
linux: featureFlags.isLinuxEnabled,
409409
macos: featureFlags.isMacOSEnabled,
410+
windows: featureFlags.isWindowsEnabled,
410411
);
411412

412413
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
507508
'You will likely need to re-create the "linux" directory after future '
508509
'Flutter updates.');
509510
}
511+
if (featureFlags.isWindowsEnabled) {
512+
globals.printStatus('');
513+
globals.printStatus('WARNING: The Windows tooling and APIs are not yet stable. '
514+
'You will likely need to re-create the "windows" directory after future '
515+
'Flutter updates.');
516+
}
510517
}
511518
return FlutterCommandResult.success();
512519
}
@@ -517,7 +524,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
517524
? stringArg('description')
518525
: 'A new flutter module project.';
519526
templateContext['description'] = description;
520-
generatedCount += _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
527+
generatedCount += await _renderTemplate(globals.fs.path.join('module', 'common'), directory, templateContext, overwrite: overwrite);
521528
if (boolArg('pub')) {
522529
await pub.get(
523530
context: PubContext.create,
@@ -536,7 +543,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
536543
? stringArg('description')
537544
: 'A new Flutter package project.';
538545
templateContext['description'] = description;
539-
generatedCount += _renderTemplate('package', directory, templateContext, overwrite: overwrite);
546+
generatedCount += await _renderTemplate('package', directory, templateContext, overwrite: overwrite);
540547
if (boolArg('pub')) {
541548
await pub.get(
542549
context: PubContext.createPackage,
@@ -553,7 +560,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
553560
? stringArg('description')
554561
: 'A new flutter plugin project.';
555562
templateContext['description'] = description;
556-
generatedCount += _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
563+
generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite);
557564
if (boolArg('pub')) {
558565
await pub.get(
559566
context: PubContext.createPlugin,
@@ -581,13 +588,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
581588

582589
Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async {
583590
int generatedCount = 0;
584-
generatedCount += _renderTemplate('app', directory, templateContext, overwrite: overwrite);
591+
generatedCount += await _renderTemplate('app', directory, templateContext, overwrite: overwrite);
585592
final FlutterProject project = FlutterProject.fromDirectory(directory);
586593
generatedCount += _injectGradleWrapper(project);
587594

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

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

@@ -651,6 +659,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
651659
'pluginClass': pluginClass,
652660
'pluginDartClass': pluginDartClass,
653661
'pluginCppHeaderGuard': projectName.toUpperCase(),
662+
'pluginProjectUUID': Uuid().generateV4().toUpperCase(),
654663
'withPluginHook': withPluginHook,
655664
'androidLanguage': androidLanguage,
656665
'iosLanguage': iosLanguage,
@@ -659,12 +668,13 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
659668
'web': web,
660669
'linux': linux,
661670
'macos': macos,
671+
'windows': windows,
662672
'year': DateTime.now().year,
663673
};
664674
}
665675

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

packages/flutter_tools/lib/src/commands/ide_config.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ class IdeConfigCommand extends FlutterCommand {
247247
}
248248

249249
int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
250-
final Template template = Template(_templateDirectory, _templateDirectory);
250+
final Template template = Template(_templateDirectory, _templateDirectory, null, fileSystem: globals.fs);
251251
return template.render(
252252
globals.fs.directory(dirPath),
253253
context,

packages/flutter_tools/lib/src/compile.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class StdoutHandler {
196196
/// Converts filesystem paths to package URIs.
197197
class PackageUriMapper {
198198
PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
199-
final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath)).map;
199+
final Map<String, Uri> packageMap = PackageMap(globals.fs.path.absolute(packagesPath), fileSystem: globals.fs).map;
200200
final bool isWindowsPath = globals.platform.isWindows && !scriptPath.startsWith('org-dartlang-app');
201201
final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString();
202202
for (final String packageName in packageMap.keys) {

packages/flutter_tools/lib/src/dart/package_map.dart

+21-13
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,35 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:meta/meta.dart';
56
// TODO(bkonyi): remove deprecated member usage, https://github.com/flutter/flutter/issues/51951
67
// ignore: deprecated_member_use
78
import 'package:package_config/packages_file.dart' as packages_file;
89

9-
import '../globals.dart' as globals;
10+
import '../base/file_system.dart';
11+
import '../globals.dart' as globals hide fs;
1012

1113
const String kPackagesFileName = '.packages';
1214

13-
Map<String, Uri> _parse(String packagesPath) {
14-
final List<int> source = globals.fs.file(packagesPath).readAsBytesSync();
15+
Map<String, Uri> _parse(String packagesPath, FileSystem fileSystem) {
16+
final List<int> source = fileSystem.file(packagesPath).readAsBytesSync();
1517
return packages_file.parse(source,
1618
Uri.file(packagesPath, windows: globals.platform.isWindows));
1719
}
1820

1921
class PackageMap {
20-
PackageMap(this.packagesPath);
22+
PackageMap(this.packagesPath, {
23+
@required FileSystem fileSystem,
24+
}) : _fileSystem = fileSystem;
2125

2226
/// Create a [PackageMap] for testing.
23-
PackageMap.test(Map<String, Uri> input)
24-
: packagesPath = '.packages',
25-
_map = input;
27+
PackageMap.test(Map<String, Uri> input, {
28+
@required FileSystem fileSystem,
29+
}) : packagesPath = '.packages',
30+
_map = input,
31+
_fileSystem = fileSystem;
32+
33+
final FileSystem _fileSystem;
2634

2735
static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;
2836

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

3947
/// Load and parses the .packages file.
4048
void load() {
41-
_map ??= _parse(packagesPath);
49+
_map ??= _parse(packagesPath, _fileSystem);
4250
}
4351

4452
Map<String, Uri> get map {
@@ -59,17 +67,17 @@ class PackageMap {
5967
if (packageBase == null) {
6068
return null;
6169
}
62-
final String packageRelativePath = globals.fs.path.joinAll(pathSegments);
63-
return packageBase.resolveUri(globals.fs.path.toUri(packageRelativePath));
70+
final String packageRelativePath = _fileSystem.path.joinAll(pathSegments);
71+
return packageBase.resolveUri(_fileSystem.path.toUri(packageRelativePath));
6472
}
6573

6674
String checkValid() {
67-
if (globals.fs.isFileSync(packagesPath)) {
75+
if (_fileSystem.isFileSync(packagesPath)) {
6876
return null;
6977
}
7078
String message = '$packagesPath does not exist.';
71-
final String pubspecPath = globals.fs.path.absolute(globals.fs.path.dirname(packagesPath), 'pubspec.yaml');
72-
if (globals.fs.isFileSync(pubspecPath)) {
79+
final String pubspecPath = _fileSystem.path.absolute(_fileSystem.path.dirname(packagesPath), 'pubspec.yaml');
80+
if (_fileSystem.isFileSync(pubspecPath)) {
7381
message += '\nDid you run "flutter pub get" in this directory?';
7482
} else {
7583
message += '\nDid you run this command from the same directory as your pubspec.yaml file?';

packages/flutter_tools/lib/src/plugins.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ List<Plugin> findPlugins(FlutterProject project) {
307307
project.directory.path,
308308
PackageMap.globalPackagesPath,
309309
);
310-
packages = PackageMap(packagesFile).map;
310+
packages = PackageMap(packagesFile, fileSystem: globals.fs).map;
311311
} on FormatException catch (e) {
312312
globals.printTrace('Invalid .packages file: $e');
313313
return plugins;

packages/flutter_tools/lib/src/project.dart

+23-23
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
460460
Map<String, String> _buildSettings;
461461

462462
Future<void> ensureReadyForPlatformSpecificTooling() async {
463-
_regenerateFromTemplateIfNeeded();
463+
await _regenerateFromTemplateIfNeeded();
464464
if (!_flutterLibRoot.existsSync()) {
465465
return;
466466
}
@@ -477,7 +477,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
477477
}
478478
}
479479

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

493493
_deleteIfExistsSync(ephemeralDirectory);
494-
_overwriteFromTemplate(
494+
await _overwriteFromTemplate(
495495
globals.fs.path.join('module', 'ios', 'library'),
496496
ephemeralDirectory,
497497
);
498498
// Add ephemeral host app, if a editable host app does not already exist.
499499
if (!_editableDirectory.existsSync()) {
500-
_overwriteFromTemplate(
500+
await _overwriteFromTemplate(
501501
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
502502
ephemeralDirectory,
503503
);
504504
if (hasPlugins(parent)) {
505-
_overwriteFromTemplate(
505+
await _overwriteFromTemplate(
506506
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
507507
ephemeralDirectory,
508508
);
@@ -542,19 +542,19 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
542542
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
543543
}
544544
_deleteIfExistsSync(ephemeralDirectory);
545-
_overwriteFromTemplate(
545+
await _overwriteFromTemplate(
546546
globals.fs.path.join('module', 'ios', 'library'),
547547
ephemeralDirectory,
548548
);
549-
_overwriteFromTemplate(
549+
await _overwriteFromTemplate(
550550
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
551551
_editableDirectory,
552552
);
553-
_overwriteFromTemplate(
553+
await _overwriteFromTemplate(
554554
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
555555
_editableDirectory,
556556
);
557-
_overwriteFromTemplate(
557+
await _overwriteFromTemplate(
558558
globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
559559
_editableDirectory,
560560
);
@@ -579,8 +579,8 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
579579
: hostAppRoot.childDirectory(_hostAppBundleName);
580580
}
581581

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

680680
Future<void> ensureReadyForPlatformSpecificTooling() async {
681681
if (isModule && _shouldRegenerateFromTemplate()) {
682-
_regenerateLibrary();
682+
await _regenerateLibrary();
683683
// Add ephemeral host app, if an editable host app does not already exist.
684684
if (!_editableHostAppDirectory.existsSync()) {
685-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
686-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
685+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
686+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
687687
}
688688
}
689689
if (!hostAppGradleRoot.existsSync()) {
@@ -704,10 +704,10 @@ class AndroidProject extends FlutterProjectPlatform {
704704
if (_editableHostAppDirectory.existsSync()) {
705705
throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
706706
}
707-
_regenerateLibrary();
708-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
709-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
710-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
707+
await _regenerateLibrary();
708+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
709+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
710+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
711711
gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
712712
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
713713
await injectPlugins(parent);
@@ -717,19 +717,19 @@ class AndroidProject extends FlutterProjectPlatform {
717717

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

720-
void _regenerateLibrary() {
720+
Future<void> _regenerateLibrary() async {
721721
_deleteIfExistsSync(ephemeralDirectory);
722-
_overwriteFromTemplate(globals.fs.path.join(
722+
await _overwriteFromTemplate(globals.fs.path.join(
723723
'module',
724724
'android',
725725
featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
726726
), ephemeralDirectory);
727-
_overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
727+
await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
728728
gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
729729
}
730730

731-
void _overwriteFromTemplate(String path, Directory target) {
732-
final Template template = Template.fromName(path);
731+
Future<void> _overwriteFromTemplate(String path, Directory target) async {
732+
final Template template = await Template.fromName(path, fileSystem: globals.fs);
733733
template.render(
734734
target,
735735
<String, dynamic>{

packages/flutter_tools/lib/src/runner/flutter_command.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,7 @@ abstract class FlutterCommand extends Command<void> {
803803

804804
// Validate the current package map only if we will not be running "pub get" later.
805805
if (parent?.name != 'pub' && !(_usesPubOption && boolArg('pub'))) {
806-
final String error = PackageMap(PackageMap.globalPackagesPath).checkValid();
806+
final String error = PackageMap(PackageMap.globalPackagesPath, fileSystem: globals.fs).checkValid();
807807
if (error != null) {
808808
throw ToolExit(error);
809809
}

0 commit comments

Comments
 (0)