| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import '../android/android.dart' as android; |
| import '../artifacts.dart'; |
| import '../base/utils.dart'; |
| import '../dart/pub.dart'; |
| import '../globals.dart'; |
| import '../template.dart'; |
| |
| class CreateCommand extends Command { |
| final String name = 'create'; |
| final String description = 'Create a new Flutter project.'; |
| final List<String> aliases = <String>['init']; |
| |
| CreateCommand() { |
| argParser.addOption('out', |
| abbr: 'o', |
| hide: true, |
| help: 'The output directory.' |
| ); |
| argParser.addFlag('pub', |
| defaultsTo: true, |
| help: 'Whether to run "pub get" after the project has been created.' |
| ); |
| argParser.addFlag( |
| 'with-driver-test', |
| negatable: true, |
| defaultsTo: false, |
| help: 'Also add Flutter Driver dependencies and generate a sample driver test.' |
| ); |
| } |
| |
| String get invocation => "${runner.executableName} $name <output directory>"; |
| |
| @override |
| Future<int> run() async { |
| if (!argResults.wasParsed('out') && argResults.rest.isEmpty) { |
| printStatus('No option specified for the output directory.'); |
| printStatus(usage); |
| return 2; |
| } |
| |
| if (ArtifactStore.flutterRoot == null) { |
| printError('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment'); |
| printError('variable was specified. Unable to find package:flutter.'); |
| return 2; |
| } |
| |
| String flutterRoot = path.absolute(ArtifactStore.flutterRoot); |
| |
| String flutterPackagesDirectory = path.join(flutterRoot, 'packages'); |
| String flutterPackagePath = path.join(flutterPackagesDirectory, 'flutter'); |
| if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml'))) { |
| printError('Unable to find package:flutter in $flutterPackagePath'); |
| return 2; |
| } |
| |
| String flutterDriverPackagePath = path.join(flutterRoot, 'packages', 'flutter_driver'); |
| if (!FileSystemEntity.isFileSync(path.join(flutterDriverPackagePath, 'pubspec.yaml'))) { |
| printError('Unable to find package:flutter_driver in $flutterDriverPackagePath'); |
| return 2; |
| } |
| |
| Directory projectDir; |
| |
| if (argResults.wasParsed('out')) { |
| projectDir = new Directory(argResults['out']); |
| } else { |
| projectDir = new Directory(argResults.rest.first); |
| } |
| |
| String dirPath = path.normalize(projectDir.absolute.path); |
| String projectName = _normalizeProjectName(path.basename(dirPath)); |
| |
| if (_validateProjectName(projectName) != null) { |
| printError(_validateProjectName(projectName)); |
| return 1; |
| } |
| |
| _renderTemplates(projectName, dirPath, flutterPackagesDirectory, |
| renderDriverTest: argResults['with-driver-test']); |
| |
| printStatus(''); |
| |
| if (argResults['pub']) { |
| int code = await pubGet(directory: dirPath); |
| if (code != 0) |
| return code; |
| } |
| |
| printStatus(''); |
| |
| // Run doctor; tell the user the next steps. |
| if (doctor.canLaunchAnything) { |
| // Let them know a summary of the state of their tooling. |
| doctor.summary(); |
| |
| printStatus(''' |
| All done! In order to run your application, type: |
| |
| \$ cd $dirPath |
| \$ flutter run |
| '''); |
| } else { |
| printStatus("You'll need to install additional components before you can run " |
| "your Flutter app:"); |
| printStatus(''); |
| |
| // Give the user more detailed analysis. |
| doctor.diagnose(); |
| printStatus(''); |
| printStatus("After installing components, run 'flutter doctor' in order to " |
| "re-validate your setup."); |
| printStatus("When complete, type 'flutter run' from the '$dirPath' " |
| "directory in order to launch your app."); |
| } |
| |
| return 0; |
| } |
| |
| void _renderTemplates(String projectName, String dirPath, |
| String flutterPackagesDirectory, { bool renderDriverTest: false }) { |
| new Directory(dirPath).createSync(recursive: true); |
| |
| flutterPackagesDirectory = path.normalize(flutterPackagesDirectory); |
| flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory); |
| |
| printStatus('Creating project ${path.basename(projectName)}:'); |
| |
| Map templateContext = <String, dynamic>{ |
| 'projectName': projectName, |
| 'androidIdentifier': _createAndroidIdentifier(projectName), |
| 'iosIdentifier': _createUTIIdentifier(projectName), |
| 'description': description, |
| 'flutterPackagesDirectory': flutterPackagesDirectory, |
| 'androidMinApiLevel': android.minApiLevel |
| }; |
| |
| if (renderDriverTest) |
| templateContext['withDriverTest?'] = <String, dynamic>{}; |
| |
| Template createTemplate = new Template.fromName('create'); |
| createTemplate.render(new Directory(dirPath), templateContext, |
| overwriteExisting: false); |
| |
| if (renderDriverTest) { |
| Template driverTemplate = new Template.fromName('driver'); |
| driverTemplate.render(new Directory(path.join(dirPath, 'test_driver')), |
| templateContext, overwriteExisting: false); |
| } |
| } |
| } |
| |
| String _normalizeProjectName(String name) { |
| name = name.replaceAll('-', '_').replaceAll(' ', '_'); |
| // Strip any extension (like .dart). |
| if (name.contains('.')) |
| name = name.substring(0, name.indexOf('.')); |
| return name; |
| } |
| |
| String _createAndroidIdentifier(String name) { |
| return 'com.yourcompany.${camelCase(name)}'; |
| } |
| |
| String _createUTIIdentifier(String name) { |
| // Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name |
| RegExp disallowed = new RegExp(r"[^a-zA-Z0-9\-.\u0080-\uffff]+"); |
| name = camelCase(name).replaceAll(disallowed, ''); |
| name = name.isEmpty ? 'untitled' : name; |
| return 'com.yourcompany.$name'; |
| } |
| |
| final Set<String> _packageDependencies = new Set<String>.from(<String>[ |
| 'args', |
| 'async', |
| 'collection', |
| 'convert', |
| 'flutter', |
| 'html', |
| 'intl', |
| 'logging', |
| 'matcher', |
| 'mime', |
| 'path', |
| 'plugin', |
| 'pool', |
| 'test', |
| 'utf', |
| 'watcher', |
| 'yaml' |
| ]); |
| |
| /// Return `null` if the project name is legal. Return a validation message if |
| /// we should disallow the project name. |
| String _validateProjectName(String projectName) { |
| if (_packageDependencies.contains(projectName)) { |
| return "Invalid project name: '$projectName' - this will conflict with Flutter " |
| "package dependencies."; |
| } |
| |
| return null; |
| } |
| |
| String _relativePath({ String from, String to }) { |
| String result = path.relative(to, from: from); |
| // `path.relative()` doesn't always return a correct result: dart-lang/path#12. |
| if (FileSystemEntity.isDirectorySync(path.join(from, result))) |
| return result; |
| return to; |
| } |