From 5259e1bc6dec8992a0fe3547cb3ec4e94f3c6a43 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 24 Oct 2022 14:20:03 -0700 Subject: [PATCH] Add --empty to the flutter create command (#113873) --- .../lib/src/commands/create.dart | 28 +++++++++++++++++-- .../lib/src/commands/create_base.dart | 2 ++ .../templates/app/README.md.tmpl | 2 ++ .../templates/app/lib/main.dart.tmpl | 23 +++++++++++++++ .../templates/app/pubspec.yaml.tmpl | 20 +++++++++++++ .../app_shared/analysis_options.yaml.tmpl | 4 +++ .../commands.shard/permeable/create_test.dart | 22 +++++++++++++++ 7 files changed, 98 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index e80f477c3ae..fd6006fa5e7 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -65,6 +65,12 @@ class CreateCommand extends CreateBase { 'https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html', valueHelp: 'id', ); + argParser.addFlag( + 'empty', + abbr: 'e', + help: 'Specifies creating using an application template with a main.dart that is minimal, ' + 'including no comments, as a starting point for a new application. Implies "--template=app".', + ); argParser.addOption( 'list-samples', help: 'Specifies a JSON output file for a listing of Flutter code samples ' @@ -191,9 +197,17 @@ class CreateCommand extends CreateBase { return FlutterCommandResult.success(); } + if (argResults!.wasParsed('empty') && argResults!.wasParsed('sample')) { + throwToolExit( + 'Only one of --empty or --sample may be specified, not both.', + exitCode: 2, + ); + } + validateOutputDirectoryArg(); String? sampleCode; final String? sampleArgument = stringArg('sample'); + final bool emptyArgument = boolArg('empty') ?? false; if (sampleArgument != null) { final String? templateArgument = stringArg('template'); if (templateArgument != null && stringToProjectType(templateArgument) != FlutterProjectType.app) { @@ -299,6 +313,7 @@ class CreateCommand extends CreateBase { flutterRoot: flutterRoot, withPlatformChannelPluginHook: generateMethodChannelsPlugin, withFfiPluginHook: generateFfiPlugin, + withEmptyMain: emptyArgument, androidLanguage: stringArgDeprecated('android-language'), iosLanguage: stringArgDeprecated('ios-language'), iosDevelopmentTeam: developmentTeam, @@ -411,11 +426,15 @@ class CreateCommand extends CreateBase { ); } if (sampleCode != null) { - generatedFileCount += _applySample(relativeDir, sampleCode); + _applySample(relativeDir, sampleCode); + } + if (sampleCode != null || emptyArgument) { + generatedFileCount += _removeTestDir(relativeDir); } globals.printStatus('Wrote $generatedFileCount files.'); globals.printStatus('\nAll done!'); - final String application = sampleCode != null ? 'sample application' : 'application'; + final String application = + '${emptyArgument ? 'empty' : ''}${sampleCode != null ? 'sample' : ''} application'; if (generatePackage) { final String relativeMainPath = globals.fs.path.normalize(globals.fs.path.join( relativeDirPath, @@ -657,10 +676,13 @@ Your $application code is in $relativeAppMain. // documentation website in sampleCode. Returns the difference in the number // of files after applying the sample, since it also deletes the application's // test directory (since the template's test doesn't apply to the sample). - int _applySample(Directory directory, String sampleCode) { + void _applySample(Directory directory, String sampleCode) { final File mainDartFile = directory.childDirectory('lib').childFile('main.dart'); mainDartFile.createSync(recursive: true); mainDartFile.writeAsStringSync(sampleCode); + } + + int _removeTestDir(Directory directory) { final Directory testDir = directory.childDirectory('test'); final List files = testDir.listSync(recursive: true); testDir.deleteSync(recursive: true); diff --git a/packages/flutter_tools/lib/src/commands/create_base.dart b/packages/flutter_tools/lib/src/commands/create_base.dart index 2f11b87b4f0..0fc09004af2 100644 --- a/packages/flutter_tools/lib/src/commands/create_base.dart +++ b/packages/flutter_tools/lib/src/commands/create_base.dart @@ -354,6 +354,7 @@ abstract class CreateBase extends FlutterCommand { String? gradleVersion, bool withPlatformChannelPluginHook = false, bool withFfiPluginHook = false, + bool withEmptyMain = false, bool ios = false, bool android = false, bool web = false, @@ -409,6 +410,7 @@ abstract class CreateBase extends FlutterCommand { 'withFfiPluginHook': withFfiPluginHook, 'withPlatformChannelPluginHook': withPlatformChannelPluginHook, 'withPluginHook': withFfiPluginHook || withPlatformChannelPluginHook, + 'withEmptyMain': withEmptyMain, 'androidLanguage': androidLanguage, 'iosLanguage': iosLanguage, 'hasIosDevelopmentTeam': iosDevelopmentTeam != null && iosDevelopmentTeam.isNotEmpty, diff --git a/packages/flutter_tools/templates/app/README.md.tmpl b/packages/flutter_tools/templates/app/README.md.tmpl index b6ab5407deb..0a74bff81f5 100644 --- a/packages/flutter_tools/templates/app/README.md.tmpl +++ b/packages/flutter_tools/templates/app/README.md.tmpl @@ -1,6 +1,7 @@ # {{projectName}} {{description}} +{{^withEmptyMain}} ## Getting Started @@ -14,3 +15,4 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. +{{/withEmptyMain}} diff --git a/packages/flutter_tools/templates/app/lib/main.dart.tmpl b/packages/flutter_tools/templates/app/lib/main.dart.tmpl index e963b8ac4af..aa5077dd460 100644 --- a/packages/flutter_tools/templates/app/lib/main.dart.tmpl +++ b/packages/flutter_tools/templates/app/lib/main.dart.tmpl @@ -1,4 +1,26 @@ import 'package:flutter/material.dart'; +{{#withEmptyMain}} + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: Text('Hello World!'), + ), + ), + ); + } +} +{{/withEmptyMain}} +{{^withEmptyMain}} {{#withPlatformChannelPluginHook}} import 'dart:async'; @@ -248,3 +270,4 @@ class _MyAppState extends State { } } {{/withFfiPluginHook}} +{{/withEmptyMain}} diff --git a/packages/flutter_tools/templates/app/pubspec.yaml.tmpl b/packages/flutter_tools/templates/app/pubspec.yaml.tmpl index 5939962600b..f615a78b941 100644 --- a/packages/flutter_tools/templates/app/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/app/pubspec.yaml.tmpl @@ -1,6 +1,25 @@ name: {{projectName}} description: {{description}} +{{#withEmptyMain}} +publish_to: 'none' +version: 0.1.0 +environment: + sdk: {{dartSdkVersionBounds}} + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true +{{/withEmptyMain}} +{{^withEmptyMain}} # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev @@ -100,3 +119,4 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages +{{/withEmptyMain}} diff --git a/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl b/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl index 61b6c4de17c..d78ad1b07aa 100644 --- a/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl +++ b/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl @@ -1,3 +1,4 @@ +{{^withEmptyMain}} # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # @@ -7,7 +8,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +{{/withEmptyMain}} include: package:flutter_lints/flutter.yaml +{{^withEmptyMain}} linter: # The lint rules applied to this project can be customized in the @@ -27,3 +30,4 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options +{{/withEmptyMain}} diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 6d217a8ed47..aad68d35726 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -1944,6 +1944,28 @@ void main() { }, ); + testUsingContext('can create an empty application project', () async { + await _createAndAnalyzeProject( + projectDir, + ['--no-pub', '--empty'], + [ + 'lib/main.dart', + 'flutter_project.iml', + 'android/app/src/main/AndroidManifest.xml', + 'ios/Flutter/AppFrameworkInfo.plist', + ], + unexpectedPaths: ['test'], + ); + expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(), + contains("Text('Hello World!')")); + expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(), + isNot(contains('int _counter'))); + expect(projectDir.childFile('analysis_options.yaml').readAsStringSync(), + isNot(contains('#'))); + expect(projectDir.childFile('README.md').readAsStringSync(), + isNot(contains('Getting Started'))); + }); + testUsingContext('can create a sample-based project', () async { await _createAndAnalyzeProject( projectDir,