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)
@ -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',
|
||||||
);
|
);
|
||||||
|
@ -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
@ -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
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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';
|
||||||
|
@ -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}')
|
||||||
|
@ -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),
|
||||||
],
|
],
|
||||||
|
@ -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.
|
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |