Skip to content

Commit

Permalink
Add pre-stable support for create on Windows (#51895)
Browse files Browse the repository at this point in the history
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 685e9d1
Show file tree
Hide file tree
Showing 47 changed files with 1,847 additions and 110 deletions.
13 changes: 11 additions & 2 deletions .gitattributes
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions dev/bots/analyze.dart
Expand Up @@ -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>[];
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/asset.dart
Expand Up @@ -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
Expand Down
24 changes: 17 additions & 7 deletions packages/flutter_tools/lib/src/commands/create.dart
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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')) {
Expand Down Expand Up @@ -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);

Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/commands/ide_config.dart
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/compile.dart
Expand Up @@ -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) {
Expand Down
34 changes: 21 additions & 13 deletions packages/flutter_tools/lib/src/dart/package_map.dart
Expand Up @@ -2,27 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

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

const String kPackagesFileName = '.packages';

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

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

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

final FileSystem _fileSystem;

static String get globalPackagesPath => _globalPackagesPath ?? kPackagesFileName;

Expand All @@ -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 {
Expand All @@ -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?';
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/plugins.dart
Expand Up @@ -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;
Expand Down
46 changes: 23 additions & 23 deletions packages/flutter_tools/lib/src/project.dart
Expand Up @@ -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;
}
Expand All @@ -477,7 +477,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
}
}

void _regenerateFromTemplateIfNeeded() {
Future<void> _regenerateFromTemplateIfNeeded() async {
if (!isModule) {
return;
}
Expand All @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand All @@ -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>{
Expand Down Expand Up @@ -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()) {
Expand All @@ -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);
Expand All @@ -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>{
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/runner/flutter_command.dart
Expand Up @@ -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);
}
Expand Down

0 comments on commit 685e9d1

Please sign in to comment.