Remove --template=skeleton and add a placeholder error message instead. (#160695)

Closes https://github.com/flutter/flutter/issues/160673.

Does the following:

- Renames `FlutterProjectType` to `FlutterTemplateType`; did some
enhanced enum cleanups while at it
- Creates a hierarchy of `RemovedFlutterTemplateType` from
`ParsedFlutterTemplateType`
- Removes the `skeleton` directory
- Merges `app_shared` back into `app` (no longer required now that
`skeleton` is removed)

Final cleanups are tracked in
https://github.com/flutter/flutter/issues/160692.

(Added @zanderso just to spot check this is what he meant by
https://github.com/flutter/flutter/issues/160673#issuecomment-2557742347)
This commit is contained in:
Matan Lurey 2024-12-23 16:02:29 -08:00 committed by GitHub
parent 62c6859e59
commit 0ffc4ce00e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
169 changed files with 665 additions and 1179 deletions

View File

@ -2443,7 +2443,7 @@ final String _kTemplateRelativePath = path.join(
'packages', 'packages',
'flutter_tools', 'flutter_tools',
'templates', 'templates',
'app_shared', 'app',
'windows.tmpl', 'windows.tmpl',
'runner', 'runner',
); );

View File

@ -19,7 +19,7 @@ See also:
1. [Flutter tool's Windows logic](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/lib/src/windows) - Builds and runs Flutter Windows apps on 1. [Flutter tool's Windows logic](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/lib/src/windows) - Builds and runs Flutter Windows apps on
the command line. the command line.
1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app_shared/windows.tmpl) - The entrypoint for Flutter Windows app. This 1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app/windows.tmpl) - The entrypoint for Flutter Windows app. This
launches the Windows embedder. launches the Windows embedder.
1. [`platform-windows` GitHub issues label](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Aplatform-windows+sort%3Aupdated-desc) 1. [`platform-windows` GitHub issues label](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Aplatform-windows+sort%3Aupdated-desc)
1. [`#hackers-desktop` Discord channel](https://discord.com/channels/608014603317936148/608020180177780791) 1. [`#hackers-desktop` Discord channel](https://discord.com/channels/608014603317936148/608020180177780791)

6
packages/flutter_tools/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Normally (from the root) we ignore .idea folders, but the ones present
# in ide_templates/ and templates/ are real folders we intend to copy as part of
# "flutter create".
!ide_templates/intellij/.idea
!templates/**/.idea

View File

@ -104,13 +104,16 @@ class CreateCommand extends FlutterCommand with CreateBase {
hide: !verboseHelp, hide: !verboseHelp,
); );
addPlatformsOptions(customHelp: kPlatformHelp); addPlatformsOptions(customHelp: kPlatformHelp);
final List<ParsedFlutterTemplateType> enabledTemplates =
ParsedFlutterTemplateType.enabledValues(featureFlags);
argParser.addOption( argParser.addOption(
'template', 'template',
abbr: 't', abbr: 't',
allowed: FlutterProjectType.enabledValues.map<String>((FlutterProjectType e) => e.cliName), allowed: enabledTemplates.map((ParsedFlutterTemplateType t) => t.cliName),
help: 'Specify the type of project to create.', help: 'Specify the type of project to create.',
valueHelp: 'type', valueHelp: 'type',
allowedHelp: CliEnum.allowedHelp(FlutterProjectType.enabledValues), allowedHelp: CliEnum.allowedHelp(enabledTemplates),
); );
argParser.addOption( argParser.addOption(
'sample', 'sample',
@ -228,13 +231,28 @@ class CreateCommand extends FlutterCommand with CreateBase {
} }
} }
FlutterProjectType _getProjectType(Directory projectDir) { FlutterTemplateType _getProjectType(Directory projectDir) {
FlutterProjectType? template; FlutterTemplateType? template;
FlutterProjectType? detectedProjectType; FlutterTemplateType? detectedProjectType;
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync(); final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
final String? templateArgument = stringArg('template'); final String? templateArgument = stringArg('template');
if (templateArgument != null) { if (templateArgument != null) {
template = FlutterProjectType.fromCliName(templateArgument); final ParsedFlutterTemplateType? parsedTemplate = ParsedFlutterTemplateType.fromCliName(
templateArgument,
);
switch (parsedTemplate) {
case RemovedFlutterTemplateType():
throwToolExit(
'The template ${parsedTemplate.cliName} is no longer available. For '
'your convenience the former help text is repeated below with context '
'about the removal and other possible resources:\n\n'
'${parsedTemplate.helpText}',
);
case FlutterTemplateType():
template = parsedTemplate;
case null:
break;
}
} }
// If the project directory exists and isn't empty, then try to determine the template // If the project directory exists and isn't empty, then try to determine the template
// type from the project directory. // type from the project directory.
@ -250,7 +268,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
); );
} }
} }
template ??= detectedProjectType ?? FlutterProjectType.app; template ??= detectedProjectType ?? FlutterTemplateType.app;
if (detectedProjectType != null && template != detectedProjectType && metadataExists) { if (detectedProjectType != null && template != detectedProjectType && metadataExists) {
// We can only be definitive that this is the wrong type if the .metadata file // We can only be definitive that this is the wrong type if the .metadata file
// exists and contains a type that doesn't match. // exists and contains a type that doesn't match.
@ -279,27 +297,27 @@ class CreateCommand extends FlutterCommand with CreateBase {
String? sampleCode; String? sampleCode;
final String? sampleArgument = stringArg('sample'); final String? sampleArgument = stringArg('sample');
final bool emptyArgument = boolArg('empty'); final bool emptyArgument = boolArg('empty');
final FlutterProjectType template = _getProjectType(projectDir); final FlutterTemplateType template = _getProjectType(projectDir);
if (sampleArgument != null) { if (sampleArgument != null) {
if (template != FlutterProjectType.app) { if (template != FlutterTemplateType.app) {
throwToolExit( throwToolExit(
'Cannot specify --sample with a project type other than ' 'Cannot specify --sample with a project type other than '
'"${FlutterProjectType.app.cliName}"', '"${FlutterTemplateType.app.cliName}"',
); );
} }
// Fetch the sample from the server. // Fetch the sample from the server.
sampleCode = await _fetchSampleFromServer(sampleArgument); sampleCode = await _fetchSampleFromServer(sampleArgument);
} }
if (emptyArgument && template != FlutterProjectType.app) { if (emptyArgument && template != FlutterTemplateType.app) {
throwToolExit('The --empty flag is only supported for the app template.'); throwToolExit('The --empty flag is only supported for the app template.');
} }
final bool generateModule = template == FlutterProjectType.module; final bool generateModule = template == FlutterTemplateType.module;
final bool generateMethodChannelsPlugin = template == FlutterProjectType.plugin; final bool generateMethodChannelsPlugin = template == FlutterTemplateType.plugin;
final bool generateFfiPackage = template == FlutterProjectType.packageFfi; final bool generateFfiPackage = template == FlutterTemplateType.packageFfi;
final bool generateFfiPlugin = template == FlutterProjectType.pluginFfi; final bool generateFfiPlugin = template == FlutterTemplateType.pluginFfi;
final bool generateFfi = generateFfiPlugin || generateFfiPackage; final bool generateFfi = generateFfiPlugin || generateFfiPackage;
final bool generatePackage = template == FlutterProjectType.package; final bool generatePackage = template == FlutterTemplateType.package;
final List<String> platforms = stringsArg('platforms'); final List<String> platforms = stringsArg('platforms');
// `--platforms` does not support module or package. // `--platforms` does not support module or package.
@ -359,7 +377,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
final bool includeLinux; final bool includeLinux;
final bool includeMacos; final bool includeMacos;
final bool includeWindows; final bool includeWindows;
if (template == FlutterProjectType.module) { if (template == FlutterTemplateType.module) {
// The module template only supports iOS and Android. // The module template only supports iOS and Android.
includeIos = true; includeIos = true;
includeAndroid = true; includeAndroid = true;
@ -367,7 +385,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
includeLinux = false; includeLinux = false;
includeMacos = false; includeMacos = false;
includeWindows = false; includeWindows = false;
} else if (template == FlutterProjectType.package) { } else if (template == FlutterTemplateType.package) {
// The package template does not supports any platform. // The package template does not supports any platform.
includeIos = false; includeIos = false;
includeAndroid = false; includeAndroid = false;
@ -443,7 +461,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
int generatedFileCount = 0; int generatedFileCount = 0;
final PubContext pubContext; final PubContext pubContext;
switch (template) { switch (template) {
case FlutterProjectType.app: case FlutterTemplateType.app:
final bool skipWidgetTestsGeneration = sampleCode != null || emptyArgument; final bool skipWidgetTestsGeneration = sampleCode != null || emptyArgument;
generatedFileCount += await generateApp( generatedFileCount += await generateApp(
@ -455,17 +473,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template, projectType: template,
); );
pubContext = PubContext.create; pubContext = PubContext.create;
case FlutterProjectType.skeleton: case FlutterTemplateType.module:
generatedFileCount += await generateApp(
<String>['skeleton'],
relativeDir,
templateContext,
overwrite: overwrite,
printStatusWhenWriting: !creatingNewProject,
generateMetadata: false,
);
pubContext = PubContext.create;
case FlutterProjectType.module:
generatedFileCount += await _generateModule( generatedFileCount += await _generateModule(
relativeDir, relativeDir,
templateContext, templateContext,
@ -473,7 +481,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
printStatusWhenWriting: !creatingNewProject, printStatusWhenWriting: !creatingNewProject,
); );
pubContext = PubContext.create; pubContext = PubContext.create;
case FlutterProjectType.package: case FlutterTemplateType.package:
generatedFileCount += await _generatePackage( generatedFileCount += await _generatePackage(
relativeDir, relativeDir,
templateContext, templateContext,
@ -481,7 +489,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
printStatusWhenWriting: !creatingNewProject, printStatusWhenWriting: !creatingNewProject,
); );
pubContext = PubContext.createPackage; pubContext = PubContext.createPackage;
case FlutterProjectType.plugin: case FlutterTemplateType.plugin:
generatedFileCount += await _generateMethodChannelPlugin( generatedFileCount += await _generateMethodChannelPlugin(
relativeDir, relativeDir,
templateContext, templateContext,
@ -490,7 +498,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template, projectType: template,
); );
pubContext = PubContext.createPlugin; pubContext = PubContext.createPlugin;
case FlutterProjectType.pluginFfi: case FlutterTemplateType.pluginFfi:
generatedFileCount += await _generateFfiPlugin( generatedFileCount += await _generateFfiPlugin(
relativeDir, relativeDir,
templateContext, templateContext,
@ -499,7 +507,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template, projectType: template,
); );
pubContext = PubContext.createPlugin; pubContext = PubContext.createPlugin;
case FlutterProjectType.packageFfi: case FlutterTemplateType.packageFfi:
generatedFileCount += await _generateFfiPackage( generatedFileCount += await _generateFfiPackage(
relativeDir, relativeDir,
templateContext, templateContext,
@ -667,7 +675,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, { Map<String, Object?> templateContext, {
bool overwrite = false, bool overwrite = false,
bool printStatusWhenWriting = true, bool printStatusWhenWriting = true,
required FlutterProjectType projectType, required FlutterTemplateType projectType,
}) async { }) async {
// Plugins only add a platform if it was requested explicitly by the user. // Plugins only add a platform if it was requested explicitly by the user.
if (!argResults!.wasParsed('platforms')) { if (!argResults!.wasParsed('platforms')) {
@ -763,7 +771,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, { Map<String, Object?> templateContext, {
bool overwrite = false, bool overwrite = false,
bool printStatusWhenWriting = true, bool printStatusWhenWriting = true,
required FlutterProjectType projectType, required FlutterTemplateType projectType,
}) async { }) async {
// Plugins only add a platform if it was requested explicitly by the user. // Plugins only add a platform if it was requested explicitly by the user.
if (!argResults!.wasParsed('platforms')) { if (!argResults!.wasParsed('platforms')) {
@ -844,7 +852,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, { Map<String, Object?> templateContext, {
bool overwrite = false, bool overwrite = false,
bool printStatusWhenWriting = true, bool printStatusWhenWriting = true,
required FlutterProjectType projectType, required FlutterTemplateType projectType,
}) async { }) async {
int generatedCount = 0; int generatedCount = 0;
final String? description = final String? description =
@ -1025,7 +1033,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
required String templateGradleVersion, required String templateGradleVersion,
required String templateAgpVersion, required String templateAgpVersion,
required String templateAgpVersionForModule, required String templateAgpVersionForModule,
required FlutterProjectType projectType, required FlutterTemplateType projectType,
required String projectDirPath, required String projectDirPath,
}) { }) {
// Determine if the Java version specified conflicts with the template Gradle or AGP version. // Determine if the Java version specified conflicts with the template Gradle or AGP version.
@ -1041,7 +1049,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
); );
String relevantTemplateAgpVersion = templateAgpVersion; String relevantTemplateAgpVersion = templateAgpVersion;
if (projectType == FlutterProjectType.module && if (projectType == FlutterTemplateType.module &&
Version.parse(templateAgpVersion)! < Version.parse(templateAgpVersionForModule)!) { Version.parse(templateAgpVersion)! < Version.parse(templateAgpVersionForModule)!) {
// If a module is being created, make sure to check for Java/AGP compatibility between the highest used version of AGP in the module template. // If a module is being created, make sure to check for Java/AGP compatibility between the highest used version of AGP in the module template.
javaAgpVersionsCompatible = gradle.validateJavaAndAgp( javaAgpVersionsCompatible = gradle.validateJavaAndAgp(
@ -1066,7 +1074,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
); );
if (!javaGradleVersionsCompatible) { if (!javaGradleVersionsCompatible) {
if (projectType == FlutterProjectType.plugin || projectType == FlutterProjectType.pluginFfi) { if (projectType == FlutterTemplateType.plugin || projectType == FlutterTemplateType.pluginFfi) {
// Only impacted files could be in sample code. // Only impacted files could be in sample code.
return; return;
} }
@ -1157,24 +1165,26 @@ globally for Flutter by running:
// Returns path of the gradle-wrapper.properties file for the specified // Returns path of the gradle-wrapper.properties file for the specified
// generated project type. // generated project type.
String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, String projectDirPath) { String? _getGradleWrapperPropertiesFilePath(
FlutterTemplateType projectType,
String projectDirPath,
) {
String gradleWrapperPropertiesFilePath = ''; String gradleWrapperPropertiesFilePath = '';
switch (projectType) { switch (projectType) {
case FlutterProjectType.app: case FlutterTemplateType.app:
case FlutterProjectType.skeleton:
gradleWrapperPropertiesFilePath = globals.fs.path.join( gradleWrapperPropertiesFilePath = globals.fs.path.join(
projectDirPath, projectDirPath,
'android/gradle/wrapper/gradle-wrapper.properties', 'android/gradle/wrapper/gradle-wrapper.properties',
); );
case FlutterProjectType.module: case FlutterTemplateType.module:
gradleWrapperPropertiesFilePath = globals.fs.path.join( gradleWrapperPropertiesFilePath = globals.fs.path.join(
projectDirPath, projectDirPath,
'.android/gradle/wrapper/gradle-wrapper.properties', '.android/gradle/wrapper/gradle-wrapper.properties',
); );
case FlutterProjectType.plugin: case FlutterTemplateType.plugin:
case FlutterProjectType.pluginFfi: case FlutterTemplateType.pluginFfi:
case FlutterProjectType.package: case FlutterTemplateType.package:
case FlutterProjectType.packageFfi: case FlutterTemplateType.packageFfi:
// TODO(camsim99): Add relevant file path for packageFfi when Android is supported. // TODO(camsim99): Add relevant file path for packageFfi when Android is supported.
// No gradle-wrapper.properties files not part of sample code that // No gradle-wrapper.properties files not part of sample code that
// can be determined. // can be determined.
@ -1186,18 +1196,17 @@ String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, Stri
// Returns the path(s) of the build.gradle file(s) for the specified generated // Returns the path(s) of the build.gradle file(s) for the specified generated
// project type. // project type.
List<String>? _getBuildGradleConfigurationFilePaths( List<String>? _getBuildGradleConfigurationFilePaths(
FlutterProjectType projectType, FlutterTemplateType projectType,
String projectDirPath, String projectDirPath,
) { ) {
final List<String> buildGradleConfigurationFilePaths = <String>[]; final List<String> buildGradleConfigurationFilePaths = <String>[];
switch (projectType) { switch (projectType) {
case FlutterProjectType.app: case FlutterTemplateType.app:
case FlutterProjectType.skeleton: case FlutterTemplateType.pluginFfi:
case FlutterProjectType.pluginFfi:
buildGradleConfigurationFilePaths.add( buildGradleConfigurationFilePaths.add(
globals.fs.path.join(projectDirPath, 'android/build.gradle'), globals.fs.path.join(projectDirPath, 'android/build.gradle'),
); );
case FlutterProjectType.module: case FlutterTemplateType.module:
const String moduleBuildGradleFilePath = '.android/build.gradle'; const String moduleBuildGradleFilePath = '.android/build.gradle';
const String moduleAppBuildGradleFlePath = '.android/app/build.gradle'; const String moduleAppBuildGradleFlePath = '.android/app/build.gradle';
const String moduleFlutterBuildGradleFilePath = '.android/Flutter/build.gradle'; const String moduleFlutterBuildGradleFilePath = '.android/Flutter/build.gradle';
@ -1206,12 +1215,12 @@ List<String>? _getBuildGradleConfigurationFilePaths(
globals.fs.path.join(projectDirPath, moduleAppBuildGradleFlePath), globals.fs.path.join(projectDirPath, moduleAppBuildGradleFlePath),
globals.fs.path.join(projectDirPath, moduleFlutterBuildGradleFilePath), globals.fs.path.join(projectDirPath, moduleFlutterBuildGradleFilePath),
]); ]);
case FlutterProjectType.plugin: case FlutterTemplateType.plugin:
buildGradleConfigurationFilePaths.add( buildGradleConfigurationFilePaths.add(
globals.fs.path.join(projectDirPath, 'android/app/build.gradle'), globals.fs.path.join(projectDirPath, 'android/app/build.gradle'),
); );
case FlutterProjectType.package: case FlutterTemplateType.package:
case FlutterProjectType.packageFfi: case FlutterTemplateType.packageFfi:
// TODO(camsim99): Add any relevant file paths for packageFfi when Android is supported. // TODO(camsim99): Add any relevant file paths for packageFfi when Android is supported.
// No build.gradle file because there is no platform-specific implementation. // No build.gradle file because there is no platform-specific implementation.
return null; return null;

View File

@ -158,7 +158,7 @@ mixin CreateBase on FlutterCommand {
/// Throws assertion if [projectDir] does not exist or empty. /// Throws assertion if [projectDir] does not exist or empty.
/// Returns null if no project type can be determined. /// Returns null if no project type can be determined.
@protected @protected
FlutterProjectType? determineTemplateType() { FlutterTemplateType? determineTemplateType() {
assert(projectDir.existsSync() && projectDir.listSync().isNotEmpty); assert(projectDir.existsSync() && projectDir.listSync().isNotEmpty);
final File metadataFile = globals.fs.file( final File metadataFile = globals.fs.file(
globals.fs.path.join(projectDir.absolute.path, '.metadata'), globals.fs.path.join(projectDir.absolute.path, '.metadata'),
@ -167,7 +167,7 @@ mixin CreateBase on FlutterCommand {
metadataFile, metadataFile,
globals.logger, globals.logger,
); );
final FlutterProjectType? projectType = projectMetadata.projectType; final FlutterTemplateType? projectType = projectMetadata.projectType;
if (projectType != null) { if (projectType != null) {
return projectType; return projectType;
} }
@ -184,7 +184,7 @@ mixin CreateBase on FlutterCommand {
if (exists(<String>['android', 'app']) || if (exists(<String>['android', 'app']) ||
exists(<String>['ios', 'Runner']) || exists(<String>['ios', 'Runner']) ||
exists(<String>['ios', 'Flutter'])) { exists(<String>['ios', 'Flutter'])) {
return FlutterProjectType.app; return FlutterTemplateType.app;
} }
// Since we can't really be definitive on nearly-empty directories, err on // Since we can't really be definitive on nearly-empty directories, err on
// the side of prudence and just say we don't know. // the side of prudence and just say we don't know.
@ -472,11 +472,11 @@ mixin CreateBase on FlutterCommand {
bool pluginExampleApp = false, bool pluginExampleApp = false,
bool printStatusWhenWriting = true, bool printStatusWhenWriting = true,
bool generateMetadata = true, bool generateMetadata = true,
FlutterProjectType? projectType, FlutterTemplateType? projectType,
}) async { }) async {
int generatedCount = 0; int generatedCount = 0;
generatedCount += await renderMerged( generatedCount += await renderMerged(
<String>[...templateNames, 'app_shared'], <String>[...templateNames],
directory, directory,
templateContext, templateContext,
overwrite: overwrite, overwrite: overwrite,

View File

@ -2,6 +2,7 @@
// 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 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
@ -12,73 +13,122 @@ import 'project.dart';
import 'template.dart'; import 'template.dart';
import 'version.dart'; import 'version.dart';
enum FlutterProjectType implements CliEnum { /// The result of parsing `--template=` for `flutter create` and related commands.
/// This is the default project with the user-managed host code. @immutable
/// It is different than the "module" template in that it exposes and doesn't sealed class ParsedFlutterTemplateType implements CliEnum {
/// manage the platform code. static const List<ParsedFlutterTemplateType> _values = <ParsedFlutterTemplateType>[
app, ...FlutterTemplateType.values,
...RemovedFlutterTemplateType.values,
];
/// A List/Detail app template that follows community best practices. /// Parses and returns a [ParsedFlutterTemplateType], if any, for [cliName].
skeleton, ///
/// If no match was found `null` is returned.
/// The is a project that has managed platform host code. It is an application with static ParsedFlutterTemplateType? fromCliName(String cliName) {
/// ephemeral .ios and .android directories that can be updated automatically. for (final ParsedFlutterTemplateType type in _values) {
module, if (cliName == type.cliName) {
/// This is a Flutter Dart package project. It doesn't have any native
/// components, only Dart.
package,
/// This is a Dart package project with external builds for native components.
packageFfi,
/// This is a native plugin project.
plugin,
/// This is an FFI native plugin project.
pluginFfi;
@override
String get cliName => snakeCase(name);
@override
String get helpText => switch (this) {
FlutterProjectType.app => '(default) Generate a Flutter application.',
FlutterProjectType.skeleton =>
'Generate a List View / Detail View Flutter application that follows community best practices.',
FlutterProjectType.package =>
'Generate a shareable Flutter project containing modular Dart code.',
FlutterProjectType.plugin =>
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
'Linux, macOS, Windows, web, or any combination of these.',
FlutterProjectType.pluginFfi =>
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, Windows, or any combination of these.',
FlutterProjectType.packageFfi =>
'Generate a shareable Dart/Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, and Windows.',
FlutterProjectType.module =>
'Generate a project to add a Flutter module to an existing Android or iOS application.',
};
static FlutterProjectType? fromCliName(String value) {
for (final FlutterProjectType type in FlutterProjectType.values) {
if (value == type.cliName) {
return type; return type;
} }
} }
return null; return null;
} }
static List<FlutterProjectType> get enabledValues { /// Returns template types that are enabled based on the current [featureFlags].
return <FlutterProjectType>[ static List<ParsedFlutterTemplateType> enabledValues(FeatureFlags featureFlags) {
for (final FlutterProjectType value in values) return _values.toList()..retainWhere((ParsedFlutterTemplateType templateType) {
if (value != FlutterProjectType.packageFfi || featureFlags.isNativeAssetsEnabled) value, return templateType.isEnabled(featureFlags);
]; });
} }
/// Whether the flag is enabled based on a flag being set.
bool isEnabled(FeatureFlags featureFlags) => true;
}
/// A [ParsedFlutterTemplateType] that is no longer operable.
///
/// The CLI can give a hint to a developer that the [cliName] _did_ use to exist,
/// but does no longer, and provides [helpText] for other resources to use instead.
enum RemovedFlutterTemplateType implements ParsedFlutterTemplateType {
skeleton(
helpText:
'Formerly generated a list view / detail view Flutter application that '
'followed some community best practices. For up to date resources, see '
'https://flutter.github.io/samples, https://docs.flutter.dev/codelabs, '
'and external resources such as https://flutter-builder.app/.',
);
const RemovedFlutterTemplateType({required this.helpText});
@override
bool isEnabled(FeatureFlags featureFlags) => true;
@override
final String helpText;
@override
String get cliName => snakeCase(name);
}
/// The result of parsing a recognized `--template` for `flutter create` and related commands.
enum FlutterTemplateType implements ParsedFlutterTemplateType {
/// The default project with the user-managed host code.
///
/// It is different than the "module" template in that it exposes and doesn't
/// manage the platform code.
app(helpText: '(default) Generate a Flutter application.'),
/// A project that has managed platform host code.
///
/// It is an application with ephemeral .ios and .android directories that can be updated automatically.
module(
helpText:
'Generate a project to add a Flutter module to an existing Android or iOS application.',
),
/// A Flutter Dart package project.
///
/// It doesn't have any native components, only Dart.
package(helpText: 'Generate a shareable Flutter project containing modular Dart code.'),
/// A Dart package project with external builds for native components.
packageFfi(
helpText:
'Generate a shareable Dart/Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, and Windows.',
),
/// A native plugin project.
plugin(
helpText:
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
'Linux, macOS, Windows, web, or any combination of these.',
),
/// This is an FFI native plugin project.
pluginFfi(
helpText:
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, Windows, or any combination of these.',
);
const FlutterTemplateType({required this.helpText});
@override
bool isEnabled(FeatureFlags featureFlags) {
return switch (this) {
FlutterTemplateType.packageFfi => featureFlags.isNativeAssetsEnabled,
_ => true,
};
}
@override
final String helpText;
@override
String get cliName => snakeCase(name);
} }
/// Verifies the expected yaml keys are present in the file. /// Verifies the expected yaml keys are present in the file.
@ -135,7 +185,13 @@ class FlutterProjectMetadata {
} }
} }
if (_validateMetadataMap(yamlRoot, <String, Type>{'project_type': String}, _logger)) { if (_validateMetadataMap(yamlRoot, <String, Type>{'project_type': String}, _logger)) {
_projectType = FlutterProjectType.fromCliName(yamlRoot['project_type'] as String); final ParsedFlutterTemplateType? templateType = ParsedFlutterTemplateType.fromCliName(
yamlRoot['project_type'] as String,
);
_projectType = switch (templateType) {
RemovedFlutterTemplateType() || null => null,
FlutterTemplateType() => templateType,
};
} }
final Object? migrationYaml = yamlRoot['migration']; final Object? migrationYaml = yamlRoot['migration'];
if (migrationYaml is YamlMap) { if (migrationYaml is YamlMap) {
@ -148,7 +204,7 @@ class FlutterProjectMetadata {
required this.file, required this.file,
required String? versionRevision, required String? versionRevision,
required String? versionChannel, required String? versionChannel,
required FlutterProjectType? projectType, required FlutterTemplateType? projectType,
required this.migrateConfig, required this.migrateConfig,
required Logger logger, required Logger logger,
}) : _logger = logger, }) : _logger = logger,
@ -165,8 +221,8 @@ class FlutterProjectMetadata {
String? _versionChannel; String? _versionChannel;
String? get versionChannel => _versionChannel; String? get versionChannel => _versionChannel;
FlutterProjectType? _projectType; FlutterTemplateType? _projectType;
FlutterProjectType? get projectType => _projectType; FlutterTemplateType? get projectType => _projectType;
/// Metadata and configuration for the migrate command. /// Metadata and configuration for the migrate command.
MigrateConfig migrateConfig; MigrateConfig migrateConfig;

View File

@ -201,7 +201,7 @@ class BuildableIOSApp extends IOSApp {
} }
String _templateImageAssetDirNameSuffix(String asset) => String _templateImageAssetDirNameSuffix(String asset) =>
globals.fs.path.join('app_shared', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset); globals.fs.path.join('app', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset);
String get _appIconAsset => 'AppIcon.appiconset'; String get _appIconAsset => 'AppIcon.appiconset';
String get _launchImageAsset => 'LaunchImage.imageset'; String get _launchImageAsset => 'LaunchImage.imageset';

View File

@ -129,7 +129,7 @@ ${_projectMetadataInformation()}
return 'No pubspec in working directory.'; return 'No pubspec in working directory.';
} }
final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger); final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger);
final FlutterProjectType? projectType = metadata.projectType; final FlutterTemplateType? projectType = metadata.projectType;
final StringBuffer description = final StringBuffer description =
StringBuffer() StringBuffer()
..writeln('**Type**: ${projectType == null ? 'malformed' : projectType.cliName}') ..writeln('**Type**: ${projectType == null ? 'malformed' : projectType.cliName}')

View File

@ -173,13 +173,22 @@ class Template {
required Logger logger, required Logger logger,
required TemplateRenderer templateRenderer, required TemplateRenderer templateRenderer,
}) async { }) async {
// TODO(matanl): Remove this once https://github.com/flutter/packages/pull/8336 is merged and published.
// See https://github.com/flutter/flutter/issues/160692.
final List<String> imageNames;
if (names.contains('app')) {
// Emulates what used to happen when app_shared existed. It still exist in package:flutter_template_images.
imageNames = <String>[...names, 'app_shared'];
} else {
imageNames = names;
}
// All named templates are placed in the 'templates' directory // All named templates are placed in the 'templates' directory
return Template._( return Template._(
<Directory>[ <Directory>[
for (final String name in names) templatePathProvider.directoryInPackage(name, fileSystem), for (final String name in names) templatePathProvider.directoryInPackage(name, fileSystem),
], ],
<Directory>[ <Directory>[
for (final String name in names) for (final String name in imageNames)
if ((await templatePathProvider.imageDirectory(name, fileSystem, logger)).existsSync()) if ((await templatePathProvider.imageDirectory(name, fileSystem, logger)).existsSync())
await templatePathProvider.imageDirectory(name, fileSystem, logger), await templatePathProvider.imageDirectory(name, fileSystem, logger),
], ],

View File

@ -1,25 +0,0 @@
This directory contains templates for `flutter create`.
The `*_shared` subdirectories provide files for multiple templates.
* `app_shared` for `app` and `skeleton`.
* `plugin_shared` for (method channel) `plugin` and `plugin_ffi`.
For example, there are two app templates: `app` (the counter app)
and `skeleton` (the more advanced list view/detail view app).
```plain
┌────────────┐
│ app_shared │
└──┬──────┬──┘
│ │
│ │
▼ ▼
┌─────┐ ┌──────────┐
│ app │ │ skeleton │
└─────┘ └──────────┘
```
Thanks to `app_shared`, the templates for `app` and `skeleton` can contain
only the files that are specific to them alone, and the rest is automatically
kept in sync.

Some files were not shown because too many files have changed in this diff Show More