Unverified Commit 9f23866a authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Rename module --> application in flutter create command. (#22565)

This renames the "module" template to the "application" template, and makes "application" the default. The existing "app" template is now deprecated.

flutter create also now recognizes the type of project in an existing directory, and is able to recreate it without having the template type explicitly specified (although you can still do that). It does this now by first looking in the .metadata file for the new project_type field, and if it doesn't find that, then it looks at the directory structure. Also, the .metadata file is now overwritten even on an existing directory so that 1) the project_type can be added to legacy projects, and 2) the version of Flutter that updated the project last is updated.

I also cleaned up a bunch of things in create_test.dart, added many more tests, and added an example test to the test/ directory in the generated output of the application template.

Fixes #22530
Fixes #22344
parent 66f1c675
...@@ -143,7 +143,7 @@ Future<void> cleanupSystem() async { ...@@ -143,7 +143,7 @@ Future<void> cleanupSystem() async {
final String gradlewBinaryName = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; final String gradlewBinaryName = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_shutdown_gradle.'); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_shutdown_gradle.');
recursiveCopy(Directory(path.join(flutterDirectory.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper')), tempDir); recursiveCopy(Directory(path.join(flutterDirectory.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper')), tempDir);
copy(File(path.join(path.join(flutterDirectory.path, 'packages', 'flutter_tools'), 'templates', 'create', 'android.tmpl', 'gradle', 'wrapper', 'gradle-wrapper.properties')), Directory(path.join(tempDir.path, 'gradle', 'wrapper'))); copy(File(path.join(path.join(flutterDirectory.path, 'packages', 'flutter_tools'), 'templates', 'app', 'android.tmpl', 'gradle', 'wrapper', 'gradle-wrapper.properties')), Directory(path.join(tempDir.path, 'gradle', 'wrapper')));
if (!Platform.isWindows) { if (!Platform.isWindows) {
await exec( await exec(
'chmod', 'chmod',
......
...@@ -56,7 +56,7 @@ Future<Null> main(List<String> args) async { ...@@ -56,7 +56,7 @@ Future<Null> main(List<String> args) async {
ChannelCommand(verboseHelp: verboseHelp), ChannelCommand(verboseHelp: verboseHelp),
CleanCommand(), CleanCommand(),
ConfigCommand(verboseHelp: verboseHelp), ConfigCommand(verboseHelp: verboseHelp),
CreateCommand(), CreateCommand(verboseHelp: verboseHelp),
DaemonCommand(hidden: !verboseHelp), DaemonCommand(hidden: !verboseHelp),
DevicesCommand(), DevicesCommand(),
DoctorCommand(verbose: verbose), DoctorCommand(verbose: verbose),
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:linter/src/rules/pub/package_names.dart' as package_names; // ignore: implementation_imports import 'package:linter/src/rules/pub/package_names.dart' as package_names; // ignore: implementation_imports
import 'package:linter/src/utils.dart' as linter_utils; // ignore: implementation_imports import 'package:linter/src/utils.dart' as linter_utils; // ignore: implementation_imports
import 'package:yaml/yaml.dart' as yaml;
import '../android/android.dart' as android; import '../android/android.dart' as android;
import '../android/android_sdk.dart' as android_sdk; import '../android/android_sdk.dart' as android_sdk;
...@@ -23,8 +24,33 @@ import '../runner/flutter_command.dart'; ...@@ -23,8 +24,33 @@ import '../runner/flutter_command.dart';
import '../template.dart'; import '../template.dart';
import '../version.dart'; import '../version.dart';
enum ProjectType {
app,
application,
module, // TODO(gspencer): deprecated -- should be removed once IntelliJ no longer uses it.
package,
plugin,
}
ProjectType _stringToProjectType(String value) {
ProjectType result;
// TODO(gspencer): remove module when it is no longer used by IntelliJ plugin.
// Module is just an alias for application.
if (value == 'module') {
value = 'application';
}
for (ProjectType type in ProjectType.values) {
if (value == getEnumName(type)) {
result = type;
break;
}
}
assert(result != null, 'Unsupported template type $value requested.');
return result;
}
class CreateCommand extends FlutterCommand { class CreateCommand extends FlutterCommand {
CreateCommand() { CreateCommand({bool verboseHelp = false }) {
argParser.addFlag('pub', argParser.addFlag('pub',
defaultsTo: true, defaultsTo: true,
help: 'Whether to run "flutter packages get" after the project has been created.' help: 'Whether to run "flutter packages get" after the project has been created.'
...@@ -39,21 +65,33 @@ class CreateCommand extends FlutterCommand { ...@@ -39,21 +65,33 @@ class CreateCommand extends FlutterCommand {
'with-driver-test', 'with-driver-test',
negatable: true, negatable: true,
defaultsTo: false, defaultsTo: false,
help: 'Also add a flutter_driver dependency and generate a sample \'flutter drive\' test.' help: "Also add a flutter_driver dependency and generate a sample 'flutter drive' test."
); );
argParser.addOption( argParser.addOption(
'template', 'template',
abbr: 't', abbr: 't',
allowed: <String>['app', 'module', 'package', 'plugin'], allowed: ProjectType.values.map<String>((ProjectType type) => getEnumName(type)),
help: 'Specify the type of project to create.', help: 'Specify the type of project to create.',
valueHelp: 'type', valueHelp: 'type',
allowedHelp: <String, String>{ allowedHelp: <String, String>{
'app': '(default) Generate a Flutter application.', getEnumName(ProjectType.application): '(default) Generate a Flutter application.',
'package': 'Generate a shareable Flutter project containing modular Dart code.', getEnumName(ProjectType.package): 'Generate a shareable Flutter project containing modular '
'plugin': 'Generate a shareable Flutter project containing an API in Dart code\n' 'Dart code.',
'with a platform-specific implementation for Android, for iOS code, or for both.', getEnumName(ProjectType.plugin): 'Generate a shareable Flutter project containing an API '
}, 'in Dart code with a platform-specific implementation for Android, for iOS code, or '
defaultsTo: 'app', 'for both.',
}..addAll(verboseHelp
? <String, String>{
getEnumName(ProjectType.app): 'Generate the legacy form of an application project. Use '
'"application" instead, unless you are working with an existing legacy app project. '
'This is not just an alias for the "application" template, it produces different '
'output.',
getEnumName(ProjectType.module): 'Legacy, deprecated form of an application project. Use '
'"application" instead. This is just an alias for the "application" template, it '
'produces the same output. It will be removed in a future release.',
}
: <String, String>{}),
defaultsTo: null,
); );
argParser.addOption( argParser.addOption(
'description', 'description',
...@@ -90,6 +128,48 @@ class CreateCommand extends FlutterCommand { ...@@ -90,6 +128,48 @@ class CreateCommand extends FlutterCommand {
@override @override
String get invocation => '${runner.executableName} $name <output directory>'; String get invocation => '${runner.executableName} $name <output directory>';
// If it has a .metadata file with the project_type in it, use that.
// If it has an android dir and an android/app dir, it's a legacy app
// If it has an android dir and an android/src dir, it's a plugin
// If it has .ios and/or .android dirs, it's an application (nee module)
// If it has an ios dir and an ios/Classes dir, it's a plugin
// If it has no ios dir, no android dir, and no .ios or .android, then it's a package.
ProjectType _determineTemplateType(Directory projectDir) {
yaml.YamlMap loadMetadata(Directory projectDir) {
if (!projectDir.existsSync())
return null;
final File metadataFile =fs.file(fs.path.join(projectDir.absolute.path, '.metadata'));
if (!metadataFile.existsSync())
return null;
return yaml.loadYaml(metadataFile.readAsStringSync());
}
// If it exists, the project type in the metadata is definitive.
final yaml.YamlMap metadata = loadMetadata(projectDir);
if (metadata != null && metadata['project_type'] != null) {
return _stringToProjectType(metadata['project_type']);
}
// There either wasn't any metadata, or it didn't contain the project type,
// so try and figure out what type of project it is from the existing
// directory structure.
if (fs.directory(fs.path.join(projectDir.absolute.path, 'android', 'app')).existsSync())
return ProjectType.app;
final bool dotPlatformDirExists = fs.directory(fs.path.join(projectDir.absolute.path, '.ios')).existsSync() ||
fs.directory(fs.path.join(projectDir.absolute.path, '.android')).existsSync();
final bool platformDirExists = fs.directory(fs.path.join(projectDir.absolute.path, 'ios')).existsSync() ||
fs.directory(fs.path.join(projectDir.absolute.path, 'android')).existsSync();
if (dotPlatformDirExists)
return ProjectType.application;
if (!platformDirExists && !dotPlatformDirExists)
return ProjectType.package;
if (platformDirExists &&
(fs.directory(fs.path.join(projectDir.absolute.path, 'ios', 'Classes')).existsSync() ||
fs.directory(fs.path.join(projectDir.absolute.path, 'android', 'src')).existsSync()))
return ProjectType.plugin;
return null;
}
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
if (argResults.rest.isEmpty) if (argResults.rest.isEmpty)
...@@ -123,13 +203,33 @@ class CreateCommand extends FlutterCommand { ...@@ -123,13 +203,33 @@ class CreateCommand extends FlutterCommand {
if (!fs.isFileSync(fs.path.join(flutterDriverPackagePath, 'pubspec.yaml'))) if (!fs.isFileSync(fs.path.join(flutterDriverPackagePath, 'pubspec.yaml')))
throwToolExit('Unable to find package:flutter_driver in $flutterDriverPackagePath', exitCode: 2); throwToolExit('Unable to find package:flutter_driver in $flutterDriverPackagePath', exitCode: 2);
final String template = argResults['template'];
final bool generateModule = template == 'module';
final bool generatePlugin = template == 'plugin';
final bool generatePackage = template == 'package';
final Directory projectDir = fs.directory(argResults.rest.first); final Directory projectDir = fs.directory(argResults.rest.first);
final String dirPath = fs.path.normalize(projectDir.absolute.path); final String dirPath = fs.path.normalize(projectDir.absolute.path);
ProjectType detectedProjectType;
if (projectDir.existsSync()) {
detectedProjectType = _determineTemplateType(projectDir);
if (detectedProjectType == null) {
throwToolExit('Sorry, unable to detect the type of project to recreate. '
'Try creating a fresh project and migrating your existing code to '
'the new project manually.');
}
}
ProjectType template;
if (argResults['template'] != null) {
template = _stringToProjectType(argResults['template']);
}
template ??= detectedProjectType ?? ProjectType.application;
if (detectedProjectType != null && template != detectedProjectType) {
throwToolExit("The requested template type '${getEnumName(template)}' doesn't match the "
"existing template type of '${getEnumName(detectedProjectType)}'.");
}
final bool generateApplication = template == ProjectType.application;
final bool generatePlugin = template == ProjectType.plugin;
final bool generatePackage = template == ProjectType.package;
String organization = argResults['org']; String organization = argResults['org'];
if (!argResults.wasParsed('org')) { if (!argResults.wasParsed('org')) {
final FlutterProject project = await FlutterProject.fromDirectory(projectDir); final FlutterProject project = await FlutterProject.fromDirectory(projectDir);
...@@ -164,54 +264,58 @@ class CreateCommand extends FlutterCommand { ...@@ -164,54 +264,58 @@ class CreateCommand extends FlutterCommand {
iosLanguage: argResults['ios-language'], iosLanguage: argResults['ios-language'],
); );
printStatus('Creating project ${fs.path.relative(dirPath)}...'); final String relativeDirPath = fs.path.relative(dirPath);
printStatus('Creating project $relativeDirPath...');
final Directory directory = fs.directory(dirPath); final Directory directory = fs.directory(dirPath);
int generatedFileCount = 0; int generatedFileCount = 0;
switch (template) { switch (template) {
case 'app': case ProjectType.app:
generatedFileCount += await _generateApp(directory, templateContext); generatedFileCount += await _generateLegacyApp(directory, templateContext);
break; break;
case 'module': case ProjectType.module:
generatedFileCount += await _generateModule(directory, templateContext); case ProjectType.application:
generatedFileCount += await _generateApplication(directory, templateContext);
break; break;
case 'package': case ProjectType.package:
generatedFileCount += await _generatePackage(directory, templateContext); generatedFileCount += await _generatePackage(directory, templateContext);
break; break;
case 'plugin': case ProjectType.plugin:
generatedFileCount += await _generatePlugin(directory, templateContext); generatedFileCount += await _generatePlugin(directory, templateContext);
break; break;
} }
printStatus('Wrote $generatedFileCount files.'); printStatus('Wrote $generatedFileCount files.');
printStatus(''); printStatus('\nAll done!');
if (generatePackage) { if (generatePackage) {
final String relativePath = fs.path.relative(dirPath); final String relativeMainPath = fs.path.normalize(fs.path.join(relativeDirPath, 'lib', '${templateContext['projectName']}.dart'));
printStatus('Your package code is in lib/${templateContext['projectName']}.dart in the $relativePath directory.'); printStatus('Your package code is in $relativeMainPath');
} else if (generateModule) { } else if (generateApplication) {
final String relativePath = fs.path.relative(dirPath); final String relativeMainPath = fs.path.normalize(fs.path.join(relativeDirPath, 'lib', 'main.dart'));
printStatus('Your module code is in lib/main.dart in the $relativePath directory.'); printStatus('Your application code is in $relativeMainPath.');
} else { } else {
// Run doctor; tell the user the next steps. // Run doctor; tell the user the next steps.
final FlutterProject project = await FlutterProject.fromPath(dirPath); final FlutterProject project = await FlutterProject.fromPath(dirPath);
final FlutterProject app = project.hasExampleApp ? project.example : project; final FlutterProject app = project.hasExampleApp ? project.example : project;
final String relativeAppPath = fs.path.relative(app.directory.path); final String relativeAppPath = fs.path.normalize(fs.path.relative(app.directory.path));
final String relativePluginPath = fs.path.relative(dirPath); final String relativeAppMain = fs.path.join(relativeAppPath, 'lib', 'main.dart');
final String relativePluginPath = fs.path.normalize(fs.path.relative(dirPath));
final String relativePluginMain = fs.path.join(relativePluginPath, 'lib', '$projectName.dart');
if (doctor.canLaunchAnything) { if (doctor.canLaunchAnything) {
// Let them know a summary of the state of their tooling. // Let them know a summary of the state of their tooling.
await doctor.summary(); await doctor.summary();
printStatus(''' printStatus('''
All done! In order to run your application, type: In order to run your application, type:
\$ cd $relativeAppPath \$ cd $relativeAppPath
\$ flutter run \$ flutter run
Your main program file is lib/main.dart in the $relativeAppPath directory. Your application code is in $relativeAppMain.
'''); ''');
if (generatePlugin) { if (generatePlugin) {
printStatus(''' printStatus('''
Your plugin code is in lib/$projectName.dart in the $relativePluginPath directory. Your plugin code is in $relativePluginMain.
Host platform code is in the android/ and ios/ directories under $relativePluginPath. Host platform code is in the "android" and "ios" directories under $relativePluginPath.
To edit platform code in an IDE see https://flutter.io/developing-packages/#edit-plugin-package. To edit platform code in an IDE see https://flutter.io/developing-packages/#edit-plugin-package.
'''); ''');
} }
...@@ -227,18 +331,18 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit ...@@ -227,18 +331,18 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
're-validate your setup.'); 're-validate your setup.');
printStatus("When complete, type 'flutter run' from the '$relativeAppPath' " printStatus("When complete, type 'flutter run' from the '$relativeAppPath' "
'directory in order to launch your app.'); 'directory in order to launch your app.');
printStatus('Your main program file is: $relativeAppPath/lib/main.dart'); printStatus('Your application code is in $relativeAppMain');
} }
} }
} }
Future<int> _generateModule(Directory directory, Map<String, dynamic> templateContext) async { Future<int> _generateApplication(Directory directory, Map<String, dynamic> templateContext) async {
int generatedCount = 0; int generatedCount = 0;
final String description = argResults.wasParsed('description') final String description = argResults.wasParsed('description')
? argResults['description'] ? argResults['description']
: 'A new flutter module project.'; : 'A new flutter application project.';
templateContext['description'] = description; templateContext['description'] = description;
generatedCount += _renderTemplate(fs.path.join('module', 'common'), directory, templateContext); generatedCount += _renderTemplate(fs.path.join('application', 'common'), directory, templateContext);
if (argResults['pub']) { if (argResults['pub']) {
await pubGet( await pubGet(
context: PubContext.create, context: PubContext.create,
...@@ -296,13 +400,13 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit ...@@ -296,13 +400,13 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
templateContext['pluginProjectName'] = projectName; templateContext['pluginProjectName'] = projectName;
templateContext['androidPluginIdentifier'] = androidPluginIdentifier; templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
generatedCount += await _generateApp(project.example.directory, templateContext); generatedCount += await _generateLegacyApp(project.example.directory, templateContext);
return generatedCount; return generatedCount;
} }
Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext) async { Future<int> _generateLegacyApp(Directory directory, Map<String, dynamic> templateContext) async {
int generatedCount = 0; int generatedCount = 0;
generatedCount += _renderTemplate('create', directory, templateContext); generatedCount += _renderTemplate('app', directory, templateContext);
final FlutterProject project = await FlutterProject.fromDirectory(directory); final FlutterProject project = await FlutterProject.fromDirectory(directory);
generatedCount += _injectGradleWrapper(project); generatedCount += _injectGradleWrapper(project);
......
...@@ -44,8 +44,8 @@ abstract class MakeHostAppEditableSubCommand extends FlutterCommand { ...@@ -44,8 +44,8 @@ abstract class MakeHostAppEditableSubCommand extends FlutterCommand {
Future<Null> validateCommand() async { Future<Null> validateCommand() async {
await super.validateCommand(); await super.validateCommand();
_project = await FlutterProject.current(); _project = await FlutterProject.current();
if (!_project.isModule) if (!_project.isApplication)
throw ToolExit("Only projects created using 'flutter create -t module' can have their host apps made editable."); throw ToolExit("Only projects created using 'flutter create -t application' can have their host apps made editable.");
} }
} }
......
...@@ -110,14 +110,14 @@ class FlutterManifest { ...@@ -110,14 +110,14 @@ class FlutterManifest {
return _flutterDescriptor['uses-material-design'] ?? false; return _flutterDescriptor['uses-material-design'] ?? false;
} }
/// True if this manifest declares a Flutter module project. /// True if this manifest declares a Flutter application project.
/// ///
/// A Flutter project is considered a module when it has a `module:` /// A Flutter project is considered an application when it has a `application:`
/// descriptor. A Flutter module project supports integration into an /// descriptor. A Flutter application project supports integration into an
/// existing host app. /// existing host app.
/// ///
/// Such a project can be created using `flutter create -t module`. /// Such a project can be created using `flutter create -t application`.
bool get isModule => _flutterDescriptor.containsKey('module'); bool get isApplication => _flutterDescriptor.containsKey('application');
/// True if this manifest declares a Flutter plugin project. /// True if this manifest declares a Flutter plugin project.
/// ///
...@@ -130,21 +130,21 @@ class FlutterManifest { ...@@ -130,21 +130,21 @@ class FlutterManifest {
bool get isPlugin => _flutterDescriptor.containsKey('plugin'); bool get isPlugin => _flutterDescriptor.containsKey('plugin');
/// Returns the Android package declared by this manifest in its /// Returns the Android package declared by this manifest in its
/// module or plugin descriptor. Returns null, if there is no /// application or plugin descriptor. Returns null, if there is no
/// such declaration. /// such declaration.
String get androidPackage { String get androidPackage {
if (isModule) if (isApplication)
return _flutterDescriptor['module']['androidPackage']; return _flutterDescriptor['application']['androidPackage'];
if (isPlugin) if (isPlugin)
return _flutterDescriptor['plugin']['androidPackage']; return _flutterDescriptor['plugin']['androidPackage'];
return null; return null;
} }
/// Returns the iOS bundle identifier declared by this manifest in its /// Returns the iOS bundle identifier declared by this manifest in its
/// module descriptor. Returns null, if there is no such declaration. /// application descriptor. Returns null, if there is no such declaration.
String get iosBundleIdentifier { String get iosBundleIdentifier {
if (isModule) if (isApplication)
return _flutterDescriptor['module']['iosBundleIdentifier']; return _flutterDescriptor['application']['iosBundleIdentifier'];
return null; return null;
} }
......
...@@ -335,7 +335,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -335,7 +335,7 @@ Future<XcodeBuildResult> buildXcodeProject({
buildInfo: buildInfo, buildInfo: buildInfo,
); );
refreshPluginsList(project); refreshPluginsList(project);
if (hasPlugins(project) || (project.isModule && project.ios.podfile.existsSync())) { if (hasPlugins(project) || (project.isApplication && project.ios.podfile.existsSync())) {
// If the Xcode project, Podfile, or Generated.xcconfig have changed since // If the Xcode project, Podfile, or Generated.xcconfig have changed since
// last run, pods should be updated. // last run, pods should be updated.
final Fingerprinter fingerprinter = Fingerprinter( final Fingerprinter fingerprinter = Fingerprinter(
......
...@@ -57,8 +57,8 @@ Future<void> updateGeneratedXcodeProperties({ ...@@ -57,8 +57,8 @@ Future<void> updateGeneratedXcodeProperties({
localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}'); localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
if (!project.isModule) { if (!project.isApplication) {
// For module projects we do not want to write the FLUTTER_FRAMEWORK_DIR // For application projects we do not want to write the FLUTTER_FRAMEWORK_DIR
// explicitly. Rather we rely on the xcode backend script and the Podfile // explicitly. Rather we rely on the xcode backend script and the Podfile
// logic to derive it from FLUTTER_ROOT and FLUTTER_BUILD_MODE. // logic to derive it from FLUTTER_ROOT and FLUTTER_BUILD_MODE.
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}'); localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}');
......
...@@ -248,7 +248,7 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug ...@@ -248,7 +248,7 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
}; };
final String registryDirectory = project.ios.pluginRegistrantHost.path; final String registryDirectory = project.ios.pluginRegistrantHost.path;
if (project.isModule) { if (project.isApplication) {
final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes'); final String registryClassesDirectory = fs.path.join(registryDirectory, 'Classes');
_renderTemplateToFile( _renderTemplateToFile(
_iosPluginRegistrantPodspecTemplate, _iosPluginRegistrantPodspecTemplate,
...@@ -297,7 +297,7 @@ Future<void> injectPlugins(FlutterProject project) async { ...@@ -297,7 +297,7 @@ Future<void> injectPlugins(FlutterProject project) async {
final List<Plugin> plugins = findPlugins(project); final List<Plugin> plugins = findPlugins(project);
await _writeAndroidPluginRegistrant(project, plugins); await _writeAndroidPluginRegistrant(project, plugins);
await _writeIOSPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, plugins);
if (!project.isModule && project.ios.hostAppRoot.existsSync()) { if (!project.isApplication && project.ios.hostAppRoot.existsSync()) {
final CocoaPods cocoaPods = CocoaPods(); final CocoaPods cocoaPods = CocoaPods();
if (plugins.isNotEmpty) if (plugins.isNotEmpty)
cocoaPods.setupPodfile(project.ios); cocoaPods.setupPodfile(project.ios);
......
...@@ -110,8 +110,8 @@ class FlutterProject { ...@@ -110,8 +110,8 @@ class FlutterProject {
FlutterManifest.empty(), FlutterManifest.empty(),
); );
/// True, if this project is a Flutter module. /// True, if this project is a Flutter application.
bool get isModule => manifest.isModule; bool get isApplication => manifest.isApplication;
/// True, if this project has an example application. /// True, if this project has an example application.
bool get hasExampleApp => _exampleDirectory(directory).existsSync(); bool get hasExampleApp => _exampleDirectory(directory).existsSync();
...@@ -132,7 +132,7 @@ class FlutterProject { ...@@ -132,7 +132,7 @@ class FlutterProject {
} }
/// Generates project files necessary to make Gradle builds work on Android /// Generates project files necessary to make Gradle builds work on Android
/// and CocoaPods+Xcode work on iOS, for app and module projects only. /// and CocoaPods+Xcode work on iOS, for app and application projects only.
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
if (!directory.existsSync() || hasExampleApp) if (!directory.existsSync() || hasExampleApp)
return; return;
...@@ -146,7 +146,7 @@ class FlutterProject { ...@@ -146,7 +146,7 @@ class FlutterProject {
/// Represents the iOS sub-project of a Flutter project. /// Represents the iOS sub-project of a Flutter project.
/// ///
/// Instances will reflect the contents of the `ios/` sub-folder of /// Instances will reflect the contents of the `ios/` sub-folder of
/// Flutter applications and the `.ios/` sub-folder of Flutter modules. /// Flutter applications and the `.ios/` sub-folder of Flutter applications.
class IosProject { class IosProject {
IosProject._(this.parent); IosProject._(this.parent);
...@@ -162,7 +162,7 @@ class IosProject { ...@@ -162,7 +162,7 @@ class IosProject {
/// This parent folder of `Runner.xcodeproj`. /// This parent folder of `Runner.xcodeproj`.
Directory get hostAppRoot { Directory get hostAppRoot {
if (!isModule || _editableDirectory.existsSync()) if (!isApplication || _editableDirectory.existsSync())
return _editableDirectory; return _editableDirectory;
return _ephemeralDirectory; return _ephemeralDirectory;
} }
...@@ -172,14 +172,14 @@ class IosProject { ...@@ -172,14 +172,14 @@ class IosProject {
/// during build. /// during build.
/// ///
/// This is the same as [hostAppRoot] except when the project is /// This is the same as [hostAppRoot] except when the project is
/// a Flutter module with an editable host app. /// a Flutter application with an editable host app.
Directory get _flutterLibRoot => isModule ? _ephemeralDirectory : _editableDirectory; Directory get _flutterLibRoot => isApplication ? _ephemeralDirectory : _editableDirectory;
/// The bundle name of the host app, `Runner.app`. /// The bundle name of the host app, `Runner.app`.
String get hostAppBundleName => '$_hostAppBundleName.app'; String get hostAppBundleName => '$_hostAppBundleName.app';
/// True, if the parent Flutter project is a module. /// True, if the parent Flutter project is an application.
bool get isModule => parent.isModule; bool get isApplication => parent.isApplication;
/// The xcode config file for [mode]. /// The xcode config file for [mode].
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig'); File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
...@@ -264,32 +264,32 @@ class IosProject { ...@@ -264,32 +264,32 @@ class IosProject {
} }
void _regenerateFromTemplateIfNeeded() { void _regenerateFromTemplateIfNeeded() {
if (!isModule) if (!isApplication)
return; return;
final bool pubspecChanged = isOlderThanReference(entity: _ephemeralDirectory, referenceFile: parent.pubspecFile); final bool pubspecChanged = isOlderThanReference(entity: _ephemeralDirectory, referenceFile: parent.pubspecFile);
final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(_ephemeralDirectory); final bool toolingChanged = Cache.instance.isOlderThanToolsStamp(_ephemeralDirectory);
if (!pubspecChanged && !toolingChanged) if (!pubspecChanged && !toolingChanged)
return; return;
_deleteIfExistsSync(_ephemeralDirectory); _deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'library'), _ephemeralDirectory);
// Add ephemeral host app, if a editable host app does not already exist. // Add ephemeral host app, if a editable host app does not already exist.
if (!_editableDirectory.existsSync()) { if (!_editableDirectory.existsSync()) {
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'host_app_ephemeral'), _ephemeralDirectory);
if (hasPlugins(parent)) { if (hasPlugins(parent)) {
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'host_app_ephemeral_cocoapods'), _ephemeralDirectory);
} }
} }
} }
Future<void> makeHostAppEditable() async { Future<void> makeHostAppEditable() async {
assert(isModule); assert(isApplication);
if (_editableDirectory.existsSync()) if (_editableDirectory.existsSync())
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.'); throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
_deleteIfExistsSync(_ephemeralDirectory); _deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'library'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'library'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral'), _editableDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'host_app_ephemeral'), _editableDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _editableDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'host_app_ephemeral_cocoapods'), _editableDirectory);
_overwriteFromTemplate(fs.path.join('module', 'ios', 'host_app_editable_cocoapods'), _editableDirectory); _overwriteFromTemplate(fs.path.join('application', 'ios', 'host_app_editable_cocoapods'), _editableDirectory);
await _updateGeneratedXcodeConfigIfNeeded(); await _updateGeneratedXcodeConfigIfNeeded();
await injectPlugins(parent); await injectPlugins(parent);
} }
...@@ -297,7 +297,7 @@ class IosProject { ...@@ -297,7 +297,7 @@ class IosProject {
File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig'); File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig');
Directory get pluginRegistrantHost { Directory get pluginRegistrantHost {
return isModule return isApplication
? _flutterLibRoot.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant') ? _flutterLibRoot.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant')
: hostAppRoot.childDirectory(_hostAppBundleName); : hostAppRoot.childDirectory(_hostAppBundleName);
} }
...@@ -319,7 +319,7 @@ class IosProject { ...@@ -319,7 +319,7 @@ class IosProject {
/// Represents the Android sub-project of a Flutter project. /// Represents the Android sub-project of a Flutter project.
/// ///
/// Instances will reflect the contents of the `android/` sub-folder of /// Instances will reflect the contents of the `android/` sub-folder of
/// Flutter applications and the `.android/` sub-folder of Flutter modules. /// Flutter applications and the `.android/` sub-folder of Flutter applications.
class AndroidProject { class AndroidProject {
AndroidProject._(this.parent); AndroidProject._(this.parent);
...@@ -333,21 +333,21 @@ class AndroidProject { ...@@ -333,21 +333,21 @@ class AndroidProject {
/// containing the `app/` subdirectory and the `settings.gradle` file that /// containing the `app/` subdirectory and the `settings.gradle` file that
/// includes it in the overall Gradle project. /// includes it in the overall Gradle project.
Directory get hostAppGradleRoot { Directory get hostAppGradleRoot {
if (!isModule || _editableHostAppDirectory.existsSync()) if (!isApplication || _editableHostAppDirectory.existsSync())
return _editableHostAppDirectory; return _editableHostAppDirectory;
return _ephemeralDirectory; return _ephemeralDirectory;
} }
/// The Gradle root directory of the Android wrapping of Flutter and plugins. /// The Gradle root directory of the Android wrapping of Flutter and plugins.
/// This is the same as [hostAppGradleRoot] except when the project is /// This is the same as [hostAppGradleRoot] except when the project is
/// a Flutter module with an editable host app. /// a Flutter application with an editable host app.
Directory get _flutterLibGradleRoot => isModule ? _ephemeralDirectory : _editableHostAppDirectory; Directory get _flutterLibGradleRoot => isApplication ? _ephemeralDirectory : _editableHostAppDirectory;
Directory get _ephemeralDirectory => parent.directory.childDirectory('.android'); Directory get _ephemeralDirectory => parent.directory.childDirectory('.android');
Directory get _editableHostAppDirectory => parent.directory.childDirectory('android'); Directory get _editableHostAppDirectory => parent.directory.childDirectory('android');
/// True, if the parent Flutter project is a module. /// True, if the parent Flutter project is an application.
bool get isModule => parent.isModule; bool get isApplication => parent.isApplication;
File get appManifestFile { File get appManifestFile {
return isUsingGradle return isUsingGradle
...@@ -376,12 +376,12 @@ class AndroidProject { ...@@ -376,12 +376,12 @@ class AndroidProject {
} }
Future<void> ensureReadyForPlatformSpecificTooling() async { Future<void> ensureReadyForPlatformSpecificTooling() async {
if (isModule && _shouldRegenerateFromTemplate()) { if (isApplication && _shouldRegenerateFromTemplate()) {
_regenerateLibrary(); _regenerateLibrary();
// Add ephemeral host app, if an editable host app does not already exist. // Add ephemeral host app, if an editable host app does not already exist.
if (!_editableHostAppDirectory.existsSync()) { if (!_editableHostAppDirectory.existsSync()) {
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'host_app_common'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_ephemeral'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'host_app_ephemeral'), _ephemeralDirectory);
} }
} }
if (!hostAppGradleRoot.existsSync()) { if (!hostAppGradleRoot.existsSync()) {
...@@ -396,13 +396,13 @@ class AndroidProject { ...@@ -396,13 +396,13 @@ class AndroidProject {
} }
Future<void> makeHostAppEditable() async { Future<void> makeHostAppEditable() async {
assert(isModule); assert(isApplication);
if (_editableHostAppDirectory.existsSync()) if (_editableHostAppDirectory.existsSync())
throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.'); throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
_regenerateLibrary(); _regenerateLibrary();
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'host_app_common'), _editableHostAppDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'host_app_editable'), _editableHostAppDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'gradle'), _editableHostAppDirectory);
gradle.injectGradleWrapper(_editableHostAppDirectory); gradle.injectGradleWrapper(_editableHostAppDirectory);
gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties')); gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
await injectPlugins(parent); await injectPlugins(parent);
...@@ -410,12 +410,12 @@ class AndroidProject { ...@@ -410,12 +410,12 @@ class AndroidProject {
File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties'); File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app'); Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isApplication ? 'Flutter' : 'app');
void _regenerateLibrary() { void _regenerateLibrary() {
_deleteIfExistsSync(_ephemeralDirectory); _deleteIfExistsSync(_ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'library'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'library'), _ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _ephemeralDirectory); _overwriteFromTemplate(fs.path.join('application', 'android', 'gradle'), _ephemeralDirectory);
gradle.injectGradleWrapper(_ephemeralDirectory); gradle.injectGradleWrapper(_ephemeralDirectory);
} }
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
} }
} }
}, },
"module": { "application": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
......
...@@ -6,3 +6,5 @@ ...@@ -6,3 +6,5 @@
version: version:
revision: {{flutterRevision}} revision: {{flutterRevision}}
channel: {{flutterChannel}} channel: {{flutterChannel}}
project_type: app
// This is a basic Flutter widget test. // This is a basic Flutter widget test.
// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter //
// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to // To perform an interaction with a widget in your test, use the WidgetTester
// find child widgets in the widget tree, read text, and verify that the values of widget properties // utility that Flutter provides. For example, you can send tap and scroll
// are correct. // gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## common ## common
Written to root of Flutter module. Written to root of Flutter application.
Adds Dart project files including `pubspec.yaml`. Adds Dart project files including `pubspec.yaml`.
......
...@@ -19,9 +19,8 @@ import io.flutter.plugins.GeneratedPluginRegistrant; ...@@ -19,9 +19,8 @@ import io.flutter.plugins.GeneratedPluginRegistrant;
/** /**
* Main entry point for using Flutter in Android applications. * Main entry point for using Flutter in Android applications.
* *
* <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling. Do not edit. * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling.
* It may be moved into flutter.jar or another library dependency of the Flutter module project * DO NOT EDIT.</p>
* at a later point.</p>
*/ */
public final class Flutter { public final class Flutter {
private Flutter() { private Flutter() {
......
...@@ -13,9 +13,8 @@ import io.flutter.view.FlutterView; ...@@ -13,9 +13,8 @@ import io.flutter.view.FlutterView;
/** /**
* A {@link Fragment} managing a {@link FlutterView}. * A {@link Fragment} managing a {@link FlutterView}.
* *
* <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling. Do not edit. * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling.
* It may be moved into flutter.jar or another library dependency of the Flutter module project * DO NOT EDIT.</p>
* at a later point.</p>
*/ */
public class FlutterFragment extends Fragment { public class FlutterFragment extends Fragment {
public static final String ARG_ROUTE = "route"; public static final String ARG_ROUTE = "route";
......
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: {{flutterRevision}}
channel: {{flutterChannel}}
project_type: application
...@@ -12,9 +12,14 @@ dependencies: ...@@ -12,9 +12,14 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
{{#withDriverTest}}
flutter_driver:
sdk: flutter
{{/withDriverTest}}
flutter: flutter:
uses-material-design: true uses-material-design: true
module: application:
androidPackage: {{androidIdentifier}} androidPackage: {{androidIdentifier}}
iosBundleIdentifier: {{iosIdentifier}} iosBundleIdentifier: {{iosIdentifier}}
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:{{projectName}}/main.dart';
{{^withPluginHook}}
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(new MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
{{/withPluginHook}}
{{#withPluginHook}}
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(new MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) =>
widget is Text && widget.data.startsWith('Running on:'),
),
findsOneWidget);
});
}
{{/withPluginHook}}
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: {{flutterRevision}}
channel: {{flutterChannel}}
project_type: package
...@@ -6,3 +6,5 @@ ...@@ -6,3 +6,5 @@
version: version:
revision: {{flutterRevision}} revision: {{flutterRevision}}
channel: {{flutterChannel}} channel: {{flutterChannel}}
project_type: plugin
...@@ -44,7 +44,7 @@ void main() { ...@@ -44,7 +44,7 @@ void main() {
arguments: <String>['create', projectPath], arguments: <String>['create', projectPath],
statusTextContains: <String>[ statusTextContains: <String>[
'All done!', 'All done!',
'Your main program file is lib/main.dart', 'Your application code is in ${fs.path.normalize(fs.path.join(fs.path.relative(projectPath), 'lib', 'main.dart'))}',
], ],
); );
expect(libMain.existsSync(), isTrue); expect(libMain.existsSync(), isTrue);
......
...@@ -23,7 +23,6 @@ const String frameworkRevision = '12345678'; ...@@ -23,7 +23,6 @@ const String frameworkRevision = '12345678';
const String frameworkChannel = 'omega'; const String frameworkChannel = 'omega';
void main() { void main() {
group('create', () {
Directory tempDir; Directory tempDir;
Directory projectDir; Directory projectDir;
FlutterVersion mockFlutterVersion; FlutterVersion mockFlutterVersion;
...@@ -44,28 +43,48 @@ void main() { ...@@ -44,28 +43,48 @@ void main() {
tryToDelete(tempDir); tryToDelete(tempDir);
}); });
// Verify that we create a project that is well-formed. // Verify that we create a default project ('application') that is
testUsingContext('project', () async { // well-formed.
await _createAndAnalyzeProject( testUsingContext('can create a default project', () async {
projectDir, await _createAndAnalyzeProject(projectDir, <String>[], <String>[
<String>[], '.android/app/',
<String>[ '.gitignore',
'android/app/src/main/java/com/example/flutterproject/MainActivity.java', '.ios/Flutter',
'ios/Runner/AppDelegate.h', '.metadata',
'ios/Runner/AppDelegate.m',
'ios/Runner/main.m',
'lib/main.dart', 'lib/main.dart',
'pubspec.yaml',
'README.md',
'test/widget_test.dart', 'test/widget_test.dart',
'flutter_project.iml', ], unexpectedPaths: <String>[
], 'android/',
); 'ios/',
]);
return _runFlutterTest(projectDir);
}, timeout: allowForRemotePubInvocation);
testUsingContext('can create a legacy module project', () async {
await _createAndAnalyzeProject(projectDir, <String>[
'--template=module',
], <String>[
'.android/app/',
'.gitignore',
'.ios/Flutter',
'.metadata',
'lib/main.dart',
'pubspec.yaml',
'README.md',
'test/widget_test.dart',
], unexpectedPaths: <String>[
'android/',
'ios/',
]);
return _runFlutterTest(projectDir); return _runFlutterTest(projectDir);
}, timeout: allowForRemotePubInvocation); }, timeout: allowForRemotePubInvocation);
testUsingContext('kotlin/swift project', () async { testUsingContext('kotlin/swift legacy app project', () async {
return _createProject( return _createProject(
projectDir, projectDir,
<String>['--no-pub', '--android-language', 'kotlin', '-i', 'swift'], <String>['--no-pub', '--template=app', '--android-language=kotlin', '--ios-language=swift'],
<String>[ <String>[
'android/app/src/main/kotlin/com/example/flutterproject/MainActivity.kt', 'android/app/src/main/kotlin/com/example/flutterproject/MainActivity.kt',
'ios/Runner/AppDelegate.swift', 'ios/Runner/AppDelegate.swift',
...@@ -92,17 +111,17 @@ void main() { ...@@ -92,17 +111,17 @@ void main() {
unexpectedPaths: <String>[ unexpectedPaths: <String>[
'android/app/src/main/java/com/example/flutterproject/MainActivity.java', 'android/app/src/main/java/com/example/flutterproject/MainActivity.java',
'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java', 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
'example/lib/main.dart',
'ios/Classes/FlutterProjectPlugin.h', 'ios/Classes/FlutterProjectPlugin.h',
'ios/Classes/FlutterProjectPlugin.m', 'ios/Classes/FlutterProjectPlugin.m',
'ios/Runner/AppDelegate.h', 'ios/Runner/AppDelegate.h',
'ios/Runner/AppDelegate.m', 'ios/Runner/AppDelegate.m',
'ios/Runner/main.m', 'ios/Runner/main.m',
'lib/main.dart', 'lib/main.dart',
'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m',
'example/lib/main.dart',
'test/widget_test.dart', 'test/widget_test.dart',
], ],
); );
...@@ -115,17 +134,16 @@ void main() { ...@@ -115,17 +134,16 @@ void main() {
<String>['--template=plugin'], <String>['--template=plugin'],
<String>[ <String>[
'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java', 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
'ios/Classes/FlutterProjectPlugin.h',
'ios/Classes/FlutterProjectPlugin.m',
'lib/flutter_project.dart',
'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java', 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
'example/ios/Runner/AppDelegate.h', 'example/ios/Runner/AppDelegate.h',
'example/ios/Runner/AppDelegate.m', 'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m', 'example/ios/Runner/main.m',
'example/lib/main.dart', 'example/lib/main.dart',
'flutter_project.iml', 'flutter_project.iml',
'ios/Classes/FlutterProjectPlugin.h',
'ios/Classes/FlutterProjectPlugin.m',
'lib/flutter_project.dart',
], ],
plugin: true,
); );
return _runFlutterTest(projectDir.childDirectory('example')); return _runFlutterTest(projectDir.childDirectory('example'));
}, timeout: allowForRemotePubInvocation); }, timeout: allowForRemotePubInvocation);
...@@ -136,14 +154,14 @@ void main() { ...@@ -136,14 +154,14 @@ void main() {
<String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'], <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'],
<String>[ <String>[
'android/src/main/kotlin/com/example/flutterproject/FlutterProjectPlugin.kt', 'android/src/main/kotlin/com/example/flutterproject/FlutterProjectPlugin.kt',
'ios/Classes/FlutterProjectPlugin.h',
'ios/Classes/FlutterProjectPlugin.m',
'ios/Classes/SwiftFlutterProjectPlugin.swift',
'lib/flutter_project.dart',
'example/android/app/src/main/kotlin/com/example/flutterprojectexample/MainActivity.kt', 'example/android/app/src/main/kotlin/com/example/flutterprojectexample/MainActivity.kt',
'example/ios/Runner/AppDelegate.swift', 'example/ios/Runner/AppDelegate.swift',
'example/ios/Runner/Runner-Bridging-Header.h', 'example/ios/Runner/Runner-Bridging-Header.h',
'example/lib/main.dart', 'example/lib/main.dart',
'ios/Classes/FlutterProjectPlugin.h',
'ios/Classes/FlutterProjectPlugin.m',
'ios/Classes/SwiftFlutterProjectPlugin.swift',
'lib/flutter_project.dart',
], ],
unexpectedPaths: <String>[ unexpectedPaths: <String>[
'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java', 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
...@@ -152,7 +170,6 @@ void main() { ...@@ -152,7 +170,6 @@ void main() {
'example/ios/Runner/AppDelegate.m', 'example/ios/Runner/AppDelegate.m',
'example/ios/Runner/main.m', 'example/ios/Runner/main.m',
], ],
plugin: true,
); );
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
...@@ -168,73 +185,50 @@ void main() { ...@@ -168,73 +185,50 @@ void main() {
'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java', 'android/src/main/java/com/example/flutterproject/FlutterProjectPlugin.java',
'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java', 'example/android/app/src/main/java/com/example/flutterprojectexample/MainActivity.java',
], ],
plugin: true,
); );
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('project with-driver-test', () async { testUsingContext('legacy app project with-driver-test', () async {
return _createAndAnalyzeProject( return _createAndAnalyzeProject(
projectDir, projectDir,
<String>['--with-driver-test'], <String>['--with-driver-test', '--template=app'],
<String>['lib/main.dart'], <String>['lib/main.dart'],
); );
}, timeout: allowForRemotePubInvocation); }, timeout: allowForRemotePubInvocation);
testUsingContext('module', () async { testUsingContext('application project with pub', () async {
return _createProject( return _createProject(projectDir, <String>[
projectDir, '--template=application'
<String>['--no-pub', '--template=module'], ], <String>[
<String>[
'.gitignore',
'.metadata',
'lib/main.dart',
'pubspec.yaml',
'README.md',
],
unexpectedPaths: <String>[
'.android/',
'android/',
'ios/',
]
);
}, timeout: allowForCreateFlutterProject);
testUsingContext('module with pub', () async {
return _createProject(
projectDir,
<String>['-t', 'module'],
<String>[
'.gitignore',
'.metadata',
'lib/main.dart',
'pubspec.lock',
'pubspec.yaml',
'README.md',
'.packages',
'.android/build.gradle', '.android/build.gradle',
'.android/Flutter/build.gradle', '.android/Flutter/build.gradle',
'.android/Flutter/src/main/AndroidManifest.xml',
'.android/Flutter/src/main/java/io/flutter/facade/Flutter.java', '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java',
'.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java', '.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java',
'.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
'.android/Flutter/src/main/AndroidManifest.xml',
'.android/gradle.properties', '.android/gradle.properties',
'.android/gradle/wrapper/gradle-wrapper.jar', '.android/gradle/wrapper/gradle-wrapper.jar',
'.android/gradle/wrapper/gradle-wrapper.properties', '.android/gradle/wrapper/gradle-wrapper.properties',
'.android/gradlew', '.android/gradlew',
'.android/gradlew.bat', '.android/gradlew.bat',
'.android/local.properties',
'.android/include_flutter.groovy', '.android/include_flutter.groovy',
'.android/local.properties',
'.android/settings.gradle', '.android/settings.gradle',
], '.gitignore',
unexpectedPaths: <String>[ '.metadata',
'.packages',
'lib/main.dart',
'pubspec.lock',
'pubspec.yaml',
'README.md',
'test/widget_test.dart',
], unexpectedPaths: <String>[
'android/', 'android/',
'ios/', 'ios/',
] ]);
);
}, timeout: allowForRemotePubInvocation); }, timeout: allowForRemotePubInvocation);
// Verify content and formatting testUsingContext('has correct content and formatting with applicaiton template', () async {
testUsingContext('content', () async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel); when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
...@@ -242,13 +236,85 @@ void main() { ...@@ -242,13 +236,85 @@ void main() {
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command); final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--org', 'com.foo.bar', projectDir.path]); await runner.run(<String>['create', '--template=application', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
void expectExists(String relPath) { void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true); expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
} }
expectExists('lib/main.dart'); expectExists('lib/main.dart');
expectExists('test/widget_test.dart');
for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
if (file is File && file.path.endsWith('.dart')) {
final String original = file.readAsStringSync();
final Process process = await Process.start(
sdkBinaryName('dartfmt'),
<String>[file.path],
workingDirectory: projectDir.path,
);
final String formatted = await process.stdout.transform(utf8.decoder).join();
expect(original, formatted, reason: file.path);
}
}
await _runFlutterTest(projectDir, target: fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
// Generated Xcode settings
final String xcodeConfigPath = fs.path.join('.ios', 'Flutter', 'Generated.xcconfig');
expectExists(xcodeConfigPath);
final File xcodeConfigFile = fs.file(fs.path.join(projectDir.path, xcodeConfigPath));
final String xcodeConfig = xcodeConfigFile.readAsStringSync();
expect(xcodeConfig, contains('FLUTTER_ROOT='));
expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
expect(xcodeConfig, contains('FLUTTER_TARGET='));
// App identification
final String xcodeProjectPath = fs.path.join('.ios', 'Runner.xcodeproj', 'project.pbxproj');
expectExists(xcodeProjectPath);
final File xcodeProjectFile = fs.file(fs.path.join(projectDir.path, xcodeProjectPath));
final String xcodeProject = xcodeProjectFile.readAsStringSync();
expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
final String versionPath = fs.path.join('.metadata');
expectExists(versionPath);
final String version = fs.file(fs.path.join(projectDir.path, versionPath)).readAsStringSync();
expect(version, contains('version:'));
expect(version, contains('revision: 12345678'));
expect(version, contains('channel: omega'));
// IntelliJ metadata
final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
expectExists(intelliJSdkMetadataPath);
final String sdkMetaContents = fs
.file(fs.path.join(
projectDir.path,
intelliJSdkMetadataPath,
))
.readAsStringSync();
expect(sdkMetaContents, contains('<root url="file:/'));
expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
}, overrides: <Type, Generator>{
FlutterVersion: () => mockFlutterVersion,
}, timeout: allowForCreateFlutterProject);
testUsingContext('has correct content and formatting with legacy app template', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
void expectExists(String relPath) {
expect(fs.isFileSync('${projectDir.path}/$relPath'), true);
}
expectExists('lib/main.dart');
expectExists('test/widget_test.dart');
for (FileSystemEntity file in projectDir.listSync(recursive: true)) { for (FileSystemEntity file in projectDir.listSync(recursive: true)) {
if (file is File && file.path.endsWith('.dart')) { if (file is File && file.path.endsWith('.dart')) {
...@@ -292,15 +358,19 @@ void main() { ...@@ -292,15 +358,19 @@ void main() {
// IntelliJ metadata // IntelliJ metadata
final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml'); final String intelliJSdkMetadataPath = fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
expectExists(intelliJSdkMetadataPath); expectExists(intelliJSdkMetadataPath);
final String sdkMetaContents = fs.file(fs.path.join(projectDir.path, intelliJSdkMetadataPath)).readAsStringSync(); final String sdkMetaContents = fs
.file(fs.path.join(
projectDir.path,
intelliJSdkMetadataPath,
))
.readAsStringSync();
expect(sdkMetaContents, contains('<root url="file:/')); expect(sdkMetaContents, contains('<root url="file:/'));
expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"')); expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FlutterVersion: () => mockFlutterVersion, FlutterVersion: () => mockFlutterVersion,
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
// Verify that we can regenerate over an existing project. testUsingContext('can re-gen default template over existing project', () async {
testUsingContext('can re-gen over existing project', () async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
...@@ -311,10 +381,100 @@ void main() { ...@@ -311,10 +381,100 @@ void main() {
await runner.run(<String>['create', '--no-pub', projectDir.path]); await runner.run(<String>['create', '--no-pub', projectDir.path]);
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen android/ folder, reusing custom org', () async { testUsingContext('can re-gen default template over existing legacy app project with no metadta and detect the type', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
// Remove the .metadata to simulate an older instantiation that didn't generate those.
fs.file(fs.path.join(projectDir.path, '.metadata')).deleteSync();
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
expect(metadata, contains('project_type: app\n'));
}, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen default template over existing legacy app project and detect the type', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
expect(metadata, contains('project_type: app\n'));
}, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
expect(metadata, contains('project_type: plugin'));
}, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen default template over existing package project and detect the type', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=package', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
final String metadata = fs.file(fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
expect(metadata, contains('project_type: package'));
}, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen application .android/ folder, reusing custom org', () async {
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '--org', 'com.bar.foo'], <String>['--template=application', '--org', 'com.bar.foo'],
<String>[],
);
projectDir.childDirectory('.android').deleteSync(recursive: true);
return _createProject(
projectDir,
<String>[],
<String>[
'.android/app/src/main/java/com/bar/foo/flutterproject/host/MainActivity.java',
],
);
}, timeout: allowForRemotePubInvocation);
testUsingContext('can re-gen application .ios/ folder, reusing custom org', () async {
await _createProject(
projectDir,
<String>['--template=application', '--org', 'com.bar.foo'],
<String>[],
);
projectDir.childDirectory('.ios').deleteSync(recursive: true);
await _createProject(projectDir, <String>[], <String>[]);
final FlutterProject project = await FlutterProject.fromDirectory(projectDir);
expect(
project.ios.productBundleIdentifier,
'com.bar.foo.flutterProject',
);
}, timeout: allowForRemotePubInvocation);
testUsingContext('can re-gen legacy app android/ folder, reusing custom org', () async {
await _createProject(
projectDir,
<String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
<String>[], <String>[],
); );
projectDir.childDirectory('android').deleteSync(recursive: true); projectDir.childDirectory('android').deleteSync(recursive: true);
...@@ -330,10 +490,10 @@ void main() { ...@@ -330,10 +490,10 @@ void main() {
); );
}, timeout: allowForCreateFlutterProject); }, timeout: allowForCreateFlutterProject);
testUsingContext('can re-gen ios/ folder, reusing custom org', () async { testUsingContext('can re-gen legacy app ios/ folder, reusing custom org', () async {
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '--org', 'com.bar.foo'], <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
<String>[], <String>[],
); );
projectDir.childDirectory('ios').deleteSync(recursive: true); projectDir.childDirectory('ios').deleteSync(recursive: true);
...@@ -348,14 +508,14 @@ void main() { ...@@ -348,14 +508,14 @@ void main() {
testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async { testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async {
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '-t', 'plugin', '--org', 'com.bar.foo'], <String>['--no-pub', '--template=plugin', '--org', 'com.bar.foo'],
<String>[], <String>[],
); );
projectDir.childDirectory('example').deleteSync(recursive: true); projectDir.childDirectory('example').deleteSync(recursive: true);
projectDir.childDirectory('ios').deleteSync(recursive: true); projectDir.childDirectory('ios').deleteSync(recursive: true);
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '-t', 'plugin'], <String>['--no-pub', '--template=plugin'],
<String>[ <String>[
'example/android/app/src/main/java/com/bar/foo/flutterprojectexample/MainActivity.java', 'example/android/app/src/main/java/com/bar/foo/flutterprojectexample/MainActivity.java',
'ios/Classes/FlutterProjectPlugin.h', 'ios/Classes/FlutterProjectPlugin.h',
...@@ -375,13 +535,13 @@ void main() { ...@@ -375,13 +535,13 @@ void main() {
testUsingContext('fails to re-gen without specified org when org is ambiguous', () async { testUsingContext('fails to re-gen without specified org when org is ambiguous', () async {
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '--org', 'com.bar.foo'], <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
<String>[], <String>[],
); );
fs.directory(fs.path.join(projectDir.path, 'ios')).deleteSync(recursive: true); fs.directory(fs.path.join(projectDir.path, 'ios')).deleteSync(recursive: true);
await _createProject( await _createProject(
projectDir, projectDir,
<String>['--no-pub', '--org', 'com.bar.baz'], <String>['--no-pub', '--template=app', '--org', 'com.bar.baz'],
<String>[], <String>[],
); );
expect( expect(
...@@ -409,8 +569,9 @@ void main() { ...@@ -409,8 +569,9 @@ void main() {
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command); final CommandRunner<Null> runner = createTestCommandRunner(command);
final File existingFile = fs.file('${projectDir.path.toString()}/bad'); final File existingFile = fs.file('${projectDir.path.toString()}/bad');
if (!existingFile.existsSync()) if (!existingFile.existsSync()) {
existingFile.createSync(recursive: true); existingFile.createSync(recursive: true);
}
expect( expect(
runner.run(<String>['create', existingFile.path]), runner.run(<String>['create', existingFile.path]),
throwsToolExit(message: 'file exists'), throwsToolExit(message: 'file exists'),
...@@ -427,7 +588,9 @@ void main() { ...@@ -427,7 +588,9 @@ void main() {
); );
}); });
testUsingContext('invokes pub offline when requested', () async { testUsingContext(
'invokes pub offline when requested',
() async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
...@@ -443,7 +606,9 @@ void main() { ...@@ -443,7 +606,9 @@ void main() {
}, },
); );
testUsingContext('invokes pub online when offline not requested', () async { testUsingContext(
'invokes pub online when offline not requested',
() async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
...@@ -458,12 +623,14 @@ void main() { ...@@ -458,12 +623,14 @@ void main() {
ProcessManager: () => loggingProcessManager, ProcessManager: () => loggingProcessManager,
}, },
); );
});
} }
Future<Null> _createProject( Future<Null> _createProject(
Directory dir, List<String> createArgs, List<String> expectedPaths, Directory dir,
{ List<String> unexpectedPaths = const <String>[], bool plugin = false}) async { List<String> createArgs,
List<String> expectedPaths, {
List<String> unexpectedPaths = const <String>[],
}) async {
Cache.flutterRoot = '../..'; Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<Null> runner = createTestCommandRunner(command); final CommandRunner<Null> runner = createTestCommandRunner(command);
...@@ -477,23 +644,28 @@ Future<Null> _createProject( ...@@ -477,23 +644,28 @@ Future<Null> _createProject(
return fs.typeSync(fullPath) != FileSystemEntityType.notFound; return fs.typeSync(fullPath) != FileSystemEntityType.notFound;
} }
final List<String> failures = <String>[];
for (String path in expectedPaths) { for (String path in expectedPaths) {
expect(pathExists(path), true, reason: '$path does not exist'); if (!pathExists(path)) {
failures.add('Path "$path" does not exist.');
}
} }
for (String path in unexpectedPaths) { for (String path in unexpectedPaths) {
expect(pathExists(path), false, reason: '$path exists'); if (pathExists(path)) {
failures.add('Path "$path" exists when it shouldn\'t.');
}
} }
expect(failures, isEmpty, reason: failures.join('\n'));
} }
Future<Null> _createAndAnalyzeProject( Future<Null> _createAndAnalyzeProject(
Directory dir, List<String> createArgs, List<String> expectedPaths, Directory dir,
{ List<String> unexpectedPaths = const <String>[], bool plugin = false }) async { List<String> createArgs,
await _createProject(dir, createArgs, expectedPaths, unexpectedPaths: unexpectedPaths, plugin: plugin); List<String> expectedPaths, {
if (plugin) { List<String> unexpectedPaths = const <String>[],
}) async {
await _createProject(dir, createArgs, expectedPaths, unexpectedPaths: unexpectedPaths);
await _analyzeProject(dir.path); await _analyzeProject(dir.path);
} else {
await _analyzeProject(dir.path);
}
} }
Future<Null> _analyzeProject(String workingDir) async { Future<Null> _analyzeProject(String workingDir) async {
...@@ -530,8 +702,9 @@ Future<Null> _runFlutterTest(Directory workingDir, {String target}) async { ...@@ -530,8 +702,9 @@ Future<Null> _runFlutterTest(Directory workingDir, {String target}) async {
..add(flutterToolsPath) ..add(flutterToolsPath)
..add('test') ..add('test')
..add('--no-color'); ..add('--no-color');
if (target != null) if (target != null) {
args.add(target); args.add(target);
}
final ProcessResult exec = await Process.run( final ProcessResult exec = await Process.run(
'$dartSdkPath/bin/dart', '$dartSdkPath/bin/dart',
......
...@@ -114,16 +114,32 @@ void main() { ...@@ -114,16 +114,32 @@ void main() {
'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', 'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
]; ];
const List<String> applicationPluginRegistrants = <String>[
'.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.h',
'.ios/Flutter/FlutterPluginRegistrant/Classes/GeneratedPluginRegistrant.m',
'.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
];
const List<String> pluginWitnesses = <String>[ const List<String> pluginWitnesses = <String>[
'.flutter-plugins', '.flutter-plugins',
'ios/Podfile', 'ios/Podfile',
]; ];
const List<String> applicationPluginWitnesses = <String>[
'.flutter-plugins',
'.ios/Podfile',
];
const Map<String, String> pluginContentWitnesses = <String, String>{ const Map<String, String> pluginContentWitnesses = <String, String>{
'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"', 'ios/Flutter/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"', 'ios/Flutter/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
}; };
const Map<String, String> applicationPluginContentWitnesses = <String, String>{
'.ios/Config/Debug.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
'.ios/Config/Release.xcconfig': '#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"',
};
void expectDependenciesResolved(String projectPath) { void expectDependenciesResolved(String projectPath) {
for (String output in pubOutput) { for (String output in pubOutput) {
expectExists(projectPath, output); expectExists(projectPath, output);
...@@ -131,13 +147,13 @@ void main() { ...@@ -131,13 +147,13 @@ void main() {
} }
void expectZeroPluginsInjected(String projectPath) { void expectZeroPluginsInjected(String projectPath) {
for (final String registrant in pluginRegistrants) { for (final String registrant in applicationPluginRegistrants) {
expectExists(projectPath, registrant); expectExists(projectPath, registrant);
} }
for (final String witness in pluginWitnesses) { for (final String witness in pluginWitnesses) {
expectNotExists(projectPath, witness); expectNotExists(projectPath, witness);
} }
pluginContentWitnesses.forEach((String witness, String content) { applicationPluginContentWitnesses.forEach((String witness, String content) {
expectNotContains(projectPath, witness, content); expectNotContains(projectPath, witness, content);
}); });
} }
...@@ -154,10 +170,22 @@ void main() { ...@@ -154,10 +170,22 @@ void main() {
}); });
} }
void expectApplicationPluginInjected(String projectPath) {
for (final String registrant in applicationPluginRegistrants) {
expectExists(projectPath, registrant);
}
for (final String witness in applicationPluginWitnesses) {
expectExists(projectPath, witness);
}
applicationPluginContentWitnesses.forEach((String witness, String content) {
expectContains(projectPath, witness, content);
});
}
void removeGeneratedFiles(String projectPath) { void removeGeneratedFiles(String projectPath) {
final Iterable<String> allFiles = <List<String>>[ final Iterable<String> allFiles = <List<String>>[
pubOutput, pubOutput,
pluginRegistrants, applicationPluginRegistrants,
pluginWitnesses, pluginWitnesses,
].expand<String>((List<String> list) => list); ].expand<String>((List<String> list) => list);
for (String path in allFiles) { for (String path in allFiles) {
...@@ -204,14 +232,13 @@ void main() { ...@@ -204,14 +232,13 @@ void main() {
await runCommandIn(projectPath, 'get'); await runCommandIn(projectPath, 'get');
expectDependenciesResolved(projectPath); expectDependenciesResolved(projectPath);
expectPluginInjected(projectPath); expectApplicationPluginInjected(projectPath);
// TODO(mravn): This test fails on the Chrome windows bot only. }, timeout: allowForRemotePubInvocation);
// Skipping until resolved.
}, timeout: allowForRemotePubInvocation, skip: true);
testUsingContext('get fetches packages and injects plugin in plugin project', () async { testUsingContext('get fetches packages and injects plugin in plugin project', () async {
final String projectPath = await createProject( final String projectPath = await createProject(
tempDir, tempDir,
arguments: <String>['-t', 'plugin', '--no-pub'], arguments: <String>['--template=plugin', '--no-pub'],
); );
final String exampleProjectPath = fs.path.join(projectPath, 'example'); final String exampleProjectPath = fs.path.join(projectPath, 'example');
removeGeneratedFiles(projectPath); removeGeneratedFiles(projectPath);
......
...@@ -355,20 +355,20 @@ flutter: ...@@ -355,20 +355,20 @@ flutter:
'''; ''';
final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
expect(flutterManifest.isEmpty, false); expect(flutterManifest.isEmpty, false);
expect(flutterManifest.isModule, false); expect(flutterManifest.isApplication, false);
expect(flutterManifest.isPlugin, false); expect(flutterManifest.isPlugin, false);
expect(flutterManifest.androidPackage, null); expect(flutterManifest.androidPackage, null);
}); });
test('allows a module declaration', () async { test('allows an application declaration', () async {
const String manifest = ''' const String manifest = '''
name: test name: test
flutter: flutter:
module: application:
androidPackage: com.example androidPackage: com.example
'''; ''';
final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest); final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
expect(flutterManifest.isModule, true); expect(flutterManifest.isApplication, true);
expect(flutterManifest.androidPackage, 'com.example'); expect(flutterManifest.androidPackage, 'com.example');
}); });
......
...@@ -90,20 +90,20 @@ void main() { ...@@ -90,20 +90,20 @@ void main() {
}); });
group('editable Android host app', () { group('editable Android host app', () {
testInMemory('fails on non-module', () async { testInMemory('fails on non-application', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
await expectLater( await expectLater(
project.android.makeHostAppEditable(), project.android.makeHostAppEditable(),
throwsA(isInstanceOf<AssertionError>()), throwsA(isInstanceOf<AssertionError>()),
); );
}); });
testInMemory('exits on already editable module', () async { testInMemory('exits on already editable application', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.android.makeHostAppEditable(); await project.android.makeHostAppEditable();
return expectToolExitLater(project.android.makeHostAppEditable(), contains('already editable')); return expectToolExitLater(project.android.makeHostAppEditable(), contains('already editable'));
}); });
testInMemory('creates android/app folder in place of .android/app', () async { testInMemory('creates android/app folder in place of .android/app', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.android.makeHostAppEditable(); await project.android.makeHostAppEditable();
expectNotExists(project.directory.childDirectory('.android').childDirectory('app')); expectNotExists(project.directory.childDirectory('.android').childDirectory('app'));
expect( expect(
...@@ -118,7 +118,7 @@ void main() { ...@@ -118,7 +118,7 @@ void main() {
); );
}); });
testInMemory('retains .android/Flutter folder and references it', () async { testInMemory('retains .android/Flutter folder and references it', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.android.makeHostAppEditable(); await project.android.makeHostAppEditable();
expectExists(project.directory.childDirectory('.android').childDirectory('Flutter')); expectExists(project.directory.childDirectory('.android').childDirectory('Flutter'));
expect( expect(
...@@ -127,7 +127,7 @@ void main() { ...@@ -127,7 +127,7 @@ void main() {
); );
}); });
testInMemory('can be redone after deletion', () async { testInMemory('can be redone after deletion', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.android.makeHostAppEditable(); await project.android.makeHostAppEditable();
project.directory.childDirectory('android').deleteSync(recursive: true); project.directory.childDirectory('android').deleteSync(recursive: true);
await project.android.makeHostAppEditable(); await project.android.makeHostAppEditable();
...@@ -173,15 +173,15 @@ void main() { ...@@ -173,15 +173,15 @@ void main() {
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
}); });
testInMemory('creates Android library in module', () async { testInMemory('creates Android library in application', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle')); expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter'))); expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
}); });
testInMemory('creates iOS pod in module', () async { testInMemory('creates iOS pod in application', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter'); final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter');
expectExists(flutter.childFile('podhelper.rb')); expectExists(flutter.childFile('podhelper.rb'));
...@@ -194,20 +194,20 @@ void main() { ...@@ -194,20 +194,20 @@ void main() {
}); });
}); });
group('module status', () { group('application status', () {
testInMemory('is known for module', () async { testInMemory('is known for application', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await anApplicationProject();
expect(project.isModule, isTrue); expect(project.isApplication, isTrue);
expect(project.android.isModule, isTrue); expect(project.android.isApplication, isTrue);
expect(project.ios.isModule, isTrue); expect(project.ios.isApplication, isTrue);
expect(project.android.hostAppGradleRoot.basename, '.android'); expect(project.android.hostAppGradleRoot.basename, '.android');
expect(project.ios.hostAppRoot.basename, '.ios'); expect(project.ios.hostAppRoot.basename, '.ios');
}); });
testInMemory('is known for non-module', () async { testInMemory('is known for non-application', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
expect(project.isModule, isFalse); expect(project.isApplication, isFalse);
expect(project.android.isModule, isFalse); expect(project.android.isApplication, isFalse);
expect(project.ios.isModule, isFalse); expect(project.ios.isApplication, isFalse);
expect(project.android.hostAppGradleRoot.basename, 'android'); expect(project.android.hostAppGradleRoot.basename, 'android');
expect(project.ios.hostAppRoot.basename, 'ios'); expect(project.ios.hostAppRoot.basename, 'ios');
}); });
...@@ -351,13 +351,13 @@ flutter: ...@@ -351,13 +351,13 @@ flutter:
return FlutterProject.fromDirectory(directory); return FlutterProject.fromDirectory(directory);
} }
Future<FlutterProject> aModuleProject() async { Future<FlutterProject> anApplicationProject() async {
final Directory directory = fs.directory('module_project'); final Directory directory = fs.directory('application_project');
directory.childFile('.packages').createSync(recursive: true); directory.childFile('.packages').createSync(recursive: true);
directory.childFile('pubspec.yaml').writeAsStringSync(''' directory.childFile('pubspec.yaml').writeAsStringSync('''
name: my_module name: my_application
flutter: flutter:
module: application:
androidPackage: com.example androidPackage: com.example
'''); ''');
return FlutterProject.fromDirectory(directory); return FlutterProject.fromDirectory(directory);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment