Materialize Flutter module, Android (#20520)
This commit is contained in:
parent
f17bb519b3
commit
d4e5e1e11e
@ -40,7 +40,12 @@ Future<Null> main() async {
|
||||
'\ndependencies:\n battery:\n package_info:\n',
|
||||
);
|
||||
await pubspec.writeAsString(content, flush: true);
|
||||
|
||||
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
|
||||
await flutter(
|
||||
'packages',
|
||||
options: <String>['get'],
|
||||
);
|
||||
});
|
||||
|
||||
section('Build Flutter module library archive');
|
||||
|
||||
@ -76,7 +81,7 @@ Future<Null> main() async {
|
||||
);
|
||||
});
|
||||
|
||||
final bool apkBuilt = exists(new File(path.join(
|
||||
final bool ephemeralHostApkBuilt = exists(new File(path.join(
|
||||
directory.path,
|
||||
'hello',
|
||||
'build',
|
||||
@ -87,10 +92,49 @@ Future<Null> main() async {
|
||||
'app-release.apk',
|
||||
)));
|
||||
|
||||
if (!apkBuilt) {
|
||||
if (!ephemeralHostApkBuilt) {
|
||||
return new TaskResult.failure('Failed to build ephemeral host .apk');
|
||||
}
|
||||
|
||||
section('Clean build');
|
||||
|
||||
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
|
||||
await flutter('clean');
|
||||
});
|
||||
|
||||
section('Materialize host app');
|
||||
|
||||
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
|
||||
await flutter(
|
||||
'materialize',
|
||||
options: <String>['android'],
|
||||
);
|
||||
});
|
||||
|
||||
section('Build materialized host app');
|
||||
|
||||
await inDirectory(new Directory(path.join(directory.path, 'hello')), () async {
|
||||
await flutter(
|
||||
'build',
|
||||
options: <String>['apk'],
|
||||
);
|
||||
});
|
||||
|
||||
final bool materializedHostApkBuilt = exists(new File(path.join(
|
||||
directory.path,
|
||||
'hello',
|
||||
'build',
|
||||
'host',
|
||||
'outputs',
|
||||
'apk',
|
||||
'release',
|
||||
'app-release.apk',
|
||||
)));
|
||||
|
||||
if (!materializedHostApkBuilt) {
|
||||
return new TaskResult.failure('Failed to build materialized host .apk');
|
||||
}
|
||||
|
||||
section('Add to Android app');
|
||||
|
||||
final Directory hostApp = new Directory(path.join(directory.path, 'hello_host_app'));
|
||||
|
@ -23,6 +23,7 @@ import 'src/commands/ide_config.dart';
|
||||
import 'src/commands/inject_plugins.dart';
|
||||
import 'src/commands/install.dart';
|
||||
import 'src/commands/logs.dart';
|
||||
import 'src/commands/materialize.dart';
|
||||
import 'src/commands/packages.dart';
|
||||
import 'src/commands/precache.dart';
|
||||
import 'src/commands/run.dart';
|
||||
@ -67,6 +68,7 @@ Future<Null> main(List<String> args) async {
|
||||
new InjectPluginsCommand(hidden: !verboseHelp),
|
||||
new InstallCommand(),
|
||||
new LogsCommand(),
|
||||
new MaterializeCommand(),
|
||||
new PackagesCommand(),
|
||||
new PrecacheCommand(),
|
||||
new RunCommand(verboseHelp: verboseHelp),
|
||||
|
@ -23,7 +23,7 @@ import '../project.dart';
|
||||
import 'android_sdk.dart';
|
||||
import 'android_studio.dart';
|
||||
|
||||
const String gradleVersion = '4.1';
|
||||
const String gradleVersion = '4.4';
|
||||
final RegExp _assembleTaskPattern = new RegExp(r'assemble([^:]+): task ');
|
||||
|
||||
GradleProject _cachedGradleProject;
|
||||
@ -44,7 +44,7 @@ final RegExp ndkMessageFilter = new RegExp(r'^(?!NDK is missing a ".*" directory
|
||||
r'|If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to .*)');
|
||||
|
||||
FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) {
|
||||
final File plugin = project.directory.childFile(
|
||||
final File plugin = project.hostAppGradleRoot.childFile(
|
||||
fs.path.join('buildSrc', 'src', 'main', 'groovy', 'FlutterPlugin.groovy'));
|
||||
if (plugin.existsSync()) {
|
||||
final String packageLine = plugin.readAsLinesSync().skip(4).first;
|
||||
@ -53,8 +53,8 @@ FlutterPluginVersion getFlutterPluginVersion(AndroidProject project) {
|
||||
}
|
||||
return FlutterPluginVersion.v1;
|
||||
}
|
||||
final File appGradle = project.directory.childFile(
|
||||
fs.path.join('app','build.gradle'));
|
||||
final File appGradle = project.hostAppGradleRoot.childFile(
|
||||
fs.path.join('app', 'build.gradle'));
|
||||
if (appGradle.existsSync()) {
|
||||
for (String line in appGradle.readAsLinesSync()) {
|
||||
if (line.contains(new RegExp(r'apply from: .*/flutter.gradle'))) {
|
||||
@ -93,13 +93,13 @@ Future<GradleProject> _gradleProject() async {
|
||||
Future<GradleProject> _readGradleProject() async {
|
||||
final FlutterProject flutterProject = await FlutterProject.current();
|
||||
final String gradle = await _ensureGradle(flutterProject);
|
||||
await updateLocalProperties(project: flutterProject);
|
||||
updateLocalProperties(project: flutterProject);
|
||||
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
|
||||
GradleProject project;
|
||||
try {
|
||||
final RunResult runResult = await runCheckedAsync(
|
||||
<String>[gradle, 'app:properties'],
|
||||
workingDirectory: flutterProject.android.directory.path,
|
||||
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
final String properties = runResult.stdout.trim();
|
||||
@ -166,7 +166,7 @@ Future<String> _ensureGradle(FlutterProject project) async {
|
||||
// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
|
||||
// of validating the Gradle executable. This may take several seconds.
|
||||
Future<String> _initializeGradle(FlutterProject project) async {
|
||||
final Directory android = project.android.directory;
|
||||
final Directory android = project.android.hostAppGradleRoot;
|
||||
final Status status = logger.startProgress('Initializing gradle...', expectSlowOperation: true);
|
||||
String gradle = _locateGradlewExecutable(android);
|
||||
if (gradle == null) {
|
||||
@ -205,13 +205,13 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
|
||||
///
|
||||
/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
|
||||
/// this will fail with a [ToolExit].
|
||||
Future<void> updateLocalProperties({
|
||||
void updateLocalProperties({
|
||||
@required FlutterProject project,
|
||||
BuildInfo buildInfo,
|
||||
bool requireAndroidSdk = true,
|
||||
}) async {
|
||||
if (requireAndroidSdk && androidSdk == null) {
|
||||
throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
|
||||
}) {
|
||||
if (requireAndroidSdk) {
|
||||
_exitIfNoAndroidSdk();
|
||||
}
|
||||
|
||||
final File localProperties = project.android.localPropertiesFile;
|
||||
@ -250,6 +250,24 @@ Future<void> updateLocalProperties({
|
||||
settings.writeContents(localProperties);
|
||||
}
|
||||
|
||||
/// Writes standard Android local properties to the specified [properties] file.
|
||||
///
|
||||
/// Writes the path to the Android SDK, if known.
|
||||
void writeLocalProperties(File properties) {
|
||||
final SettingsFile settings = new SettingsFile();
|
||||
if (androidSdk != null) {
|
||||
settings.values['sdk.dir'] = escapePath(androidSdk.directory);
|
||||
}
|
||||
settings.writeContents(properties);
|
||||
}
|
||||
|
||||
/// Throws a ToolExit, if the path to the Android SDK is not known.
|
||||
void _exitIfNoAndroidSdk() {
|
||||
if (androidSdk == null) {
|
||||
throwToolExit('Unable to locate Android SDK. Please run `flutter doctor` for more details.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> buildGradleProject({
|
||||
@required FlutterProject project,
|
||||
@required BuildInfo buildInfo,
|
||||
@ -263,7 +281,7 @@ Future<Null> buildGradleProject({
|
||||
// and can be overwritten with flutter build command.
|
||||
// The default Gradle script reads the version name and number
|
||||
// from the local.properties file.
|
||||
await updateLocalProperties(project: project, buildInfo: buildInfo);
|
||||
updateLocalProperties(project: project, buildInfo: buildInfo);
|
||||
|
||||
final String gradle = await _ensureGradle(project);
|
||||
|
||||
@ -284,7 +302,7 @@ Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async
|
||||
final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
|
||||
final int exitCode = await runCommandAndStreamOutput(
|
||||
<String>[fs.file(gradle).absolute.path, 'build'],
|
||||
workingDirectory: project.android.directory.path,
|
||||
workingDirectory: project.android.hostAppGradleRoot.path,
|
||||
allowReentrantFlutter: true,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
@ -361,7 +379,7 @@ Future<Null> _buildGradleProjectV2(
|
||||
command.add(assembleTask);
|
||||
final int exitCode = await runCommandAndStreamOutput(
|
||||
command,
|
||||
workingDirectory: flutterProject.android.directory.path,
|
||||
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
|
||||
allowReentrantFlutter: true,
|
||||
environment: _gradleEnv,
|
||||
filter: logger.isVerbose ? null : ndkMessageFilter,
|
||||
|
@ -107,7 +107,7 @@ class AndroidApk extends ApplicationPackage {
|
||||
apkFile = fs.file(fs.path.join(getAndroidBuildDirectory(), 'app.apk'));
|
||||
}
|
||||
|
||||
final File manifest = androidProject.gradleManifestFile;
|
||||
final File manifest = androidProject.appManifestFile;
|
||||
|
||||
if (!manifest.existsSync())
|
||||
return null;
|
||||
|
@ -286,8 +286,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
|
||||
);
|
||||
}
|
||||
final FlutterProject project = await FlutterProject.fromDirectory(directory);
|
||||
if (android_sdk.androidSdk != null)
|
||||
await gradle.updateLocalProperties(project: project);
|
||||
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
|
||||
|
||||
final String projectName = templateContext['projectName'];
|
||||
final String organization = templateContext['organization'];
|
||||
@ -320,8 +319,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
|
||||
await project.ensureReadyForPlatformSpecificTooling();
|
||||
}
|
||||
|
||||
if (android_sdk.androidSdk != null)
|
||||
await gradle.updateLocalProperties(project: project);
|
||||
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
|
||||
|
||||
return generatedCount;
|
||||
}
|
||||
@ -373,7 +371,7 @@ To edit platform code in an IDE see https://flutter.io/developing-packages/#edit
|
||||
int filesCreated = 0;
|
||||
copyDirectorySync(
|
||||
cache.getArtifactDirectory('gradle_wrapper'),
|
||||
project.android.directory,
|
||||
project.android.hostAppGradleRoot,
|
||||
(File sourceFile, File destinationFile) {
|
||||
filesCreated++;
|
||||
final String modes = sourceFile.statSync().modeString();
|
||||
|
78
packages/flutter_tools/lib/src/commands/materialize.dart
Normal file
78
packages/flutter_tools/lib/src/commands/materialize.dart
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2018 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 'package:meta/meta.dart';
|
||||
import '../base/common.dart';
|
||||
import '../project.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
|
||||
class MaterializeCommand extends FlutterCommand {
|
||||
MaterializeCommand() {
|
||||
addSubcommand(new MaterializeAndroidCommand());
|
||||
addSubcommand(new MaterializeIosCommand());
|
||||
}
|
||||
|
||||
@override
|
||||
final String name = 'materialize';
|
||||
|
||||
@override
|
||||
final String description = 'Commands for materializing host apps for a Flutter Module';
|
||||
|
||||
@override
|
||||
bool get hidden => true;
|
||||
|
||||
@override
|
||||
Future<Null> runCommand() async { }
|
||||
}
|
||||
|
||||
abstract class MaterializeSubCommand extends FlutterCommand {
|
||||
MaterializeSubCommand() {
|
||||
requiresPubspecYaml();
|
||||
}
|
||||
|
||||
FlutterProject _project;
|
||||
|
||||
@override
|
||||
@mustCallSuper
|
||||
Future<Null> runCommand() async {
|
||||
await _project.ensureReadyForPlatformSpecificTooling();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Null> validateCommand() async {
|
||||
await super.validateCommand();
|
||||
_project = await FlutterProject.current();
|
||||
if (!_project.isModule)
|
||||
throw new ToolExit("Only projects created using 'flutter create -t module' can be materialized.");
|
||||
}
|
||||
}
|
||||
|
||||
class MaterializeAndroidCommand extends MaterializeSubCommand {
|
||||
@override
|
||||
String get name => 'android';
|
||||
|
||||
@override
|
||||
String get description => 'Materialize an Android host app';
|
||||
|
||||
@override
|
||||
Future<Null> runCommand() async {
|
||||
await super.runCommand();
|
||||
await _project.android.materialize();
|
||||
}
|
||||
}
|
||||
|
||||
class MaterializeIosCommand extends MaterializeSubCommand {
|
||||
@override
|
||||
String get name => 'ios';
|
||||
|
||||
@override
|
||||
String get description => 'Materialize an iOS host app';
|
||||
|
||||
@override
|
||||
Future<Null> runCommand() async {
|
||||
await super.runCommand();
|
||||
await _project.ios.materialize();
|
||||
}
|
||||
}
|
@ -329,7 +329,7 @@ class _DevFSHttpWriter {
|
||||
}
|
||||
|
||||
class DevFS {
|
||||
/// Create a [DevFS] named [fsName] for the local files in [directory].
|
||||
/// Create a [DevFS] named [fsName] for the local files in [rootDirectory].
|
||||
DevFS(VMService serviceProtocol,
|
||||
this.fsName,
|
||||
this.rootDirectory, {
|
||||
|
@ -105,7 +105,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
|
||||
return oldContents != newContents;
|
||||
}
|
||||
|
||||
/// Returns the contents of the `.flutter-plugins` file in [directory], or
|
||||
/// Returns the contents of the `.flutter-plugins` file in [project], or
|
||||
/// null if that file does not exist.
|
||||
String _readFlutterPluginsList(FlutterProject project) {
|
||||
return project.flutterPluginsFile.existsSync()
|
||||
@ -295,8 +295,7 @@ Future<void> injectPlugins(FlutterProject project) async {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the Flutter project at the specified [directory]
|
||||
/// has any plugin dependencies.
|
||||
/// Returns whether the specified Flutter [project] has any plugin dependencies.
|
||||
bool hasPlugins(FlutterProject project) {
|
||||
return _readFlutterPluginsList(project) != null;
|
||||
}
|
||||
|
@ -186,6 +186,10 @@ class IosProject {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> materialize() async {
|
||||
throwToolExit('flutter materialize has not yet been implemented for iOS');
|
||||
}
|
||||
|
||||
bool _shouldRegenerateFromTemplate() {
|
||||
return Cache.instance.fileOlderThanToolsStamp(directory.childFile('podhelper.rb'));
|
||||
}
|
||||
@ -212,62 +216,112 @@ class AndroidProject {
|
||||
/// The parent of this project.
|
||||
final FlutterProject parent;
|
||||
|
||||
/// The directory of this project.
|
||||
Directory get directory => parent.directory.childDirectory(isModule ? '.android' : 'android');
|
||||
/// The Gradle root directory of the Android host app. This is the directory
|
||||
/// containing the `app/` subdirectory and the `settings.gradle` file that
|
||||
/// includes it in the overall Gradle project.
|
||||
Directory get hostAppGradleRoot {
|
||||
if (!isModule || _materializedDirectory.existsSync())
|
||||
return _materializedDirectory;
|
||||
return _ephemeralDirectory;
|
||||
}
|
||||
|
||||
/// The Gradle root directory of the Android wrapping of Flutter and plugins.
|
||||
/// This is the same as [hostAppGradleRoot] except when the project is
|
||||
/// a Flutter module with a materialized host app.
|
||||
Directory get _flutterLibGradleRoot => isModule ? _ephemeralDirectory : _materializedDirectory;
|
||||
|
||||
Directory get _ephemeralDirectory => parent.directory.childDirectory('.android');
|
||||
Directory get _materializedDirectory => parent.directory.childDirectory('android');
|
||||
|
||||
/// True, if the parent Flutter project is a module.
|
||||
bool get isModule => parent.isModule;
|
||||
|
||||
File get gradleManifestFile {
|
||||
File get appManifestFile {
|
||||
return isUsingGradle()
|
||||
? fs.file(fs.path.join(directory.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
|
||||
: directory.childFile('AndroidManifest.xml');
|
||||
? fs.file(fs.path.join(hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
|
||||
: hostAppGradleRoot.childFile('AndroidManifest.xml');
|
||||
}
|
||||
|
||||
File get gradleAppOutV1File => gradleAppOutV1Directory.childFile('app-debug.apk');
|
||||
|
||||
Directory get gradleAppOutV1Directory {
|
||||
return fs.directory(fs.path.join(directory.path, 'app', 'build', 'outputs', 'apk'));
|
||||
return fs.directory(fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk'));
|
||||
}
|
||||
|
||||
bool isUsingGradle() {
|
||||
return directory.childFile('build.gradle').existsSync();
|
||||
return hostAppGradleRoot.childFile('build.gradle').existsSync();
|
||||
}
|
||||
|
||||
Future<String> applicationId() {
|
||||
final File gradleFile = directory.childDirectory('app').childFile('build.gradle');
|
||||
final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
|
||||
return _firstMatchInFile(gradleFile, _applicationIdPattern).then((Match match) => match?.group(1));
|
||||
}
|
||||
|
||||
Future<String> group() {
|
||||
final File gradleFile = directory.childFile('build.gradle');
|
||||
final File gradleFile = hostAppGradleRoot.childFile('build.gradle');
|
||||
return _firstMatchInFile(gradleFile, _groupPattern).then((Match match) => match?.group(1));
|
||||
}
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
if (isModule && _shouldRegenerateFromTemplate()) {
|
||||
final Template template = new Template.fromName(fs.path.join('module', 'android'));
|
||||
template.render(
|
||||
directory,
|
||||
<String, dynamic>{
|
||||
'androidIdentifier': parent.manifest.androidPackage,
|
||||
},
|
||||
printStatusWhenWriting: false,
|
||||
);
|
||||
gradle.injectGradleWrapper(directory);
|
||||
_regenerateLibrary();
|
||||
// Add ephemeral host app, if a materialized host app does not already exist.
|
||||
if (!_materializedDirectory.existsSync()) {
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _ephemeralDirectory);
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_ephemeral'), _ephemeralDirectory);
|
||||
}
|
||||
}
|
||||
if (!directory.existsSync())
|
||||
if (!hostAppGradleRoot.existsSync()) {
|
||||
return;
|
||||
await gradle.updateLocalProperties(project: parent, requireAndroidSdk: false);
|
||||
}
|
||||
gradle.updateLocalProperties(project: parent, requireAndroidSdk: false);
|
||||
}
|
||||
|
||||
bool _shouldRegenerateFromTemplate() {
|
||||
return Cache.instance.fileOlderThanToolsStamp(directory.childFile('build.gradle'));
|
||||
return Cache.instance.fileOlderThanToolsStamp(_ephemeralDirectory.childFile('build.gradle'));
|
||||
}
|
||||
|
||||
File get localPropertiesFile => directory.childFile('local.properties');
|
||||
Future<void> materialize() async {
|
||||
assert(isModule);
|
||||
if (_materializedDirectory.existsSync())
|
||||
throwToolExit('Android host app already materialized. To redo materialization, delete the android/ folder.');
|
||||
_regenerateLibrary();
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _materializedDirectory);
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_materialized'), _materializedDirectory);
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _materializedDirectory);
|
||||
gradle.injectGradleWrapper(_materializedDirectory);
|
||||
gradle.writeLocalProperties(_materializedDirectory.childFile('local.properties'));
|
||||
await injectPlugins(parent);
|
||||
}
|
||||
|
||||
Directory get pluginRegistrantHost => directory.childDirectory(isModule ? 'Flutter' : 'app');
|
||||
File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
|
||||
|
||||
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
|
||||
|
||||
void _regenerateLibrary() {
|
||||
_deleteIfExistsSync(_ephemeralDirectory);
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'library'), _ephemeralDirectory);
|
||||
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _ephemeralDirectory);
|
||||
gradle.injectGradleWrapper(_ephemeralDirectory);
|
||||
}
|
||||
|
||||
void _deleteIfExistsSync(Directory directory) {
|
||||
if (directory.existsSync())
|
||||
directory.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
void _overwriteFromTemplate(String path, Directory target) {
|
||||
final Template template = new Template.fromName(path);
|
||||
template.render(
|
||||
target,
|
||||
<String, dynamic>{
|
||||
'projectName': parent.manifest.appName,
|
||||
'androidIdentifier': parent.manifest.androidPackage,
|
||||
},
|
||||
printStatusWhenWriting: false,
|
||||
overwriteExisting: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously returns the first line-based match for [regExp] in [file].
|
||||
|
@ -905,7 +905,7 @@ Future<String> getMissingPackageHintForPlatform(TargetPlatform platform) async {
|
||||
case TargetPlatform.android_x64:
|
||||
case TargetPlatform.android_x86:
|
||||
final FlutterProject project = await FlutterProject.current();
|
||||
final String manifestPath = fs.path.relative(project.android.gradleManifestFile.path);
|
||||
final String manifestPath = fs.path.relative(project.android.appManifestFile.path);
|
||||
return 'Is your project missing an $manifestPath?\nConsider running "flutter create ." to create one.';
|
||||
case TargetPlatform.ios:
|
||||
return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.';
|
||||
|
67
packages/flutter_tools/templates/module/README.md
Normal file
67
packages/flutter_tools/templates/module/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Templates for Flutter Module
|
||||
|
||||
## common
|
||||
|
||||
Written to root of Flutter module.
|
||||
|
||||
Adds Dart project files including `pubspec.yaml`.
|
||||
|
||||
## android
|
||||
|
||||
#### library
|
||||
|
||||
Written to the `.android/` hidden folder.
|
||||
|
||||
Contents wraps Flutter/Dart code as a Gradle project that defines an
|
||||
Android library.
|
||||
|
||||
Executing `./gradlew flutter:assembleDebug` in that folder produces
|
||||
a `.aar` archive.
|
||||
|
||||
Android host apps can set up a dependency to this project to consume
|
||||
Flutter views.
|
||||
|
||||
#### gradle
|
||||
|
||||
Written to `.android/` or `android/`.
|
||||
|
||||
Mixin for adding Gradle boilerplate to Android projects. The `build.gradle`
|
||||
file is a template file so that it is created, not copied, on instantiation.
|
||||
That way, its timestamp reflects template instantiation time.
|
||||
|
||||
#### host_app_common
|
||||
|
||||
Written to either `.android/` or `android/`.
|
||||
|
||||
Contents define a single-Activity, single-View Android host app
|
||||
with a dependency on the `.android/Flutter` library.
|
||||
|
||||
Executing `./gradlew app:assembleDebug` in the target folder produces
|
||||
an `.apk` archive.
|
||||
|
||||
Used with either `android_host_ephemeral` or `android_host_materialized`.
|
||||
|
||||
#### host_app_ephemeral
|
||||
|
||||
Written to `.android/` on top of `android_host_common`.
|
||||
|
||||
Combined contents define an *ephemeral* (hidden, auto-generated,
|
||||
under Flutter tooling control) Android host app with a dependency on the
|
||||
`.android/Flutter` library.
|
||||
|
||||
#### host_app_materialized
|
||||
|
||||
Written to `android/` on top of `android_host_common`.
|
||||
|
||||
Combined contents define a *materialized* (visible, one-time generated,
|
||||
under app author control) Android host app with a dependency on the
|
||||
`.android/Flutter` library.
|
||||
|
||||
## ios
|
||||
|
||||
Written to the `.ios/` hidden folder.
|
||||
|
||||
Contents wraps Flutter/Dart code as a CocoaPods pod.
|
||||
|
||||
iOS host apps can set up a dependency to this project to consume
|
||||
Flutter views.
|
@ -1,6 +0,0 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
@ -7,7 +7,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="{{projectName}}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
@ -0,0 +1,5 @@
|
||||
// Generated file. Do not edit.
|
||||
include ':app'
|
||||
|
||||
setBinding(new Binding([gradle: this]))
|
||||
evaluate(new File('../.android/include_flutter.groovy'))
|
@ -0,0 +1,5 @@
|
||||
// Generated file. Do not edit.
|
||||
|
||||
rootProject.name = 'android_generated'
|
||||
setBinding(new Binding([gradle: this]))
|
||||
evaluate(new File('include_flutter.groovy'))
|
@ -33,7 +33,7 @@ void main() {
|
||||
// This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit
|
||||
// will be null and our expectation would fail. That would remind us to make these tests
|
||||
// hermetic before adding Android SDKs to the bots.
|
||||
await updateLocalProperties(project: await FlutterProject.current());
|
||||
updateLocalProperties(project: await FlutterProject.current());
|
||||
} on Exception catch (e) {
|
||||
shouldBeToolExit = e;
|
||||
}
|
||||
@ -190,7 +190,7 @@ someOtherProperty: someOtherValue
|
||||
writeSchemaFile(fs, schemaData);
|
||||
|
||||
try {
|
||||
await updateLocalProperties(
|
||||
updateLocalProperties(
|
||||
project: await FlutterProject.fromPath('path/to/project'),
|
||||
buildInfo: buildInfo,
|
||||
);
|
||||
|
@ -27,17 +27,6 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
Future<Null> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
|
||||
try {
|
||||
await future;
|
||||
fail('ToolExit expected, but nothing thrown');
|
||||
} on ToolExit catch(e) {
|
||||
expect(e.message, messageMatcher);
|
||||
} catch(e, trace) {
|
||||
fail('ToolExit expected, got $e\n$trace');
|
||||
}
|
||||
}
|
||||
|
||||
testInMemory('fails on invalid pubspec.yaml', () async {
|
||||
final Directory directory = fs.directory('myproject');
|
||||
directory.childFile('pubspec.yaml')
|
||||
@ -95,16 +84,55 @@ void main() {
|
||||
fs.currentDirectory.absolute.path,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('materialize Android', () {
|
||||
testInMemory('fails on non-module', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
await expectLater(
|
||||
project.android.materialize(),
|
||||
throwsA(const isInstanceOf<AssertionError>()),
|
||||
);
|
||||
});
|
||||
testInMemory('exits on already materialized module', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
await project.android.materialize();
|
||||
expectToolExitLater(project.android.materialize(), contains('already materialized'));
|
||||
});
|
||||
testInMemory('creates android/app folder in place of .android/app', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
await project.android.materialize();
|
||||
expectNotExists(project.directory.childDirectory('.android').childDirectory('app'));
|
||||
expect(
|
||||
project.directory.childDirectory('.android').childFile('settings.gradle').readAsStringSync(),
|
||||
isNot(contains("include ':app'")),
|
||||
);
|
||||
expectExists(project.directory.childDirectory('android').childDirectory('app'));
|
||||
expectExists(project.directory.childDirectory('android').childFile('local.properties'));
|
||||
expect(
|
||||
project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
|
||||
contains("include ':app'"),
|
||||
);
|
||||
});
|
||||
testInMemory('retains .android/Flutter folder and references it', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
await project.android.materialize();
|
||||
expectExists(project.directory.childDirectory('.android').childDirectory('Flutter'));
|
||||
expect(
|
||||
project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
|
||||
contains('../.android/include_flutter.groovy'),
|
||||
);
|
||||
});
|
||||
testInMemory('can be redone after deletion', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
await project.android.materialize();
|
||||
project.directory.childDirectory('android').deleteSync(recursive: true);
|
||||
await project.android.materialize();
|
||||
expectExists(project.directory.childDirectory('android').childDirectory('app'));
|
||||
});
|
||||
});
|
||||
|
||||
group('ensure ready for platform-specific tooling', () {
|
||||
void expectExists(FileSystemEntity entity) {
|
||||
expect(entity.existsSync(), isTrue);
|
||||
}
|
||||
void expectNotExists(FileSystemEntity entity) {
|
||||
expect(entity.existsSync(), isFalse);
|
||||
}
|
||||
testInMemory('does nothing, if project is not created', () async {
|
||||
final FlutterProject project = new FlutterProject(
|
||||
fs.directory('not_created'),
|
||||
@ -118,9 +146,9 @@ void main() {
|
||||
final FlutterProject project = await aPluginProject();
|
||||
await project.ensureReadyForPlatformSpecificTooling();
|
||||
expectNotExists(project.ios.directory.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
|
||||
expectNotExists(androidPluginRegistrant(project.android.directory.childDirectory('app')));
|
||||
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
|
||||
expectNotExists(project.ios.directory.childDirectory('Flutter').childFile('Generated.xcconfig'));
|
||||
expectNotExists(project.android.directory.childFile('local.properties'));
|
||||
expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
||||
});
|
||||
testInMemory('injects plugins for iOS', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
@ -135,19 +163,19 @@ void main() {
|
||||
testInMemory('injects plugins for Android', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
await project.ensureReadyForPlatformSpecificTooling();
|
||||
expectExists(androidPluginRegistrant(project.android.directory.childDirectory('app')));
|
||||
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
|
||||
});
|
||||
testInMemory('updates local properties for Android', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
await project.ensureReadyForPlatformSpecificTooling();
|
||||
expectExists(project.android.directory.childFile('local.properties'));
|
||||
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
||||
});
|
||||
testInMemory('creates Android library in module', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
await project.ensureReadyForPlatformSpecificTooling();
|
||||
expectExists(project.android.directory.childFile('settings.gradle'));
|
||||
expectExists(project.android.directory.childFile('local.properties'));
|
||||
expectExists(androidPluginRegistrant(project.android.directory.childDirectory('Flutter')));
|
||||
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
|
||||
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
||||
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
|
||||
});
|
||||
testInMemory('creates iOS pod in module', () async {
|
||||
final FlutterProject project = await aModuleProject();
|
||||
@ -169,7 +197,7 @@ void main() {
|
||||
expect(project.isModule, isTrue);
|
||||
expect(project.android.isModule, isTrue);
|
||||
expect(project.ios.isModule, isTrue);
|
||||
expect(project.android.directory.basename, '.android');
|
||||
expect(project.android.hostAppGradleRoot.basename, '.android');
|
||||
expect(project.ios.directory.basename, '.ios');
|
||||
});
|
||||
testInMemory('is known for non-module', () async {
|
||||
@ -177,7 +205,7 @@ void main() {
|
||||
expect(project.isModule, isFalse);
|
||||
expect(project.android.isModule, isFalse);
|
||||
expect(project.ios.isModule, isFalse);
|
||||
expect(project.android.directory.basename, 'android');
|
||||
expect(project.android.hostAppGradleRoot.basename, 'android');
|
||||
expect(project.ios.directory.basename, 'ios');
|
||||
});
|
||||
});
|
||||
@ -326,6 +354,25 @@ void transfer(FileSystemEntity entity, FileSystem target) {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
|
||||
try {
|
||||
await future;
|
||||
fail('ToolExit expected, but nothing thrown');
|
||||
} on ToolExit catch(e) {
|
||||
expect(e.message, messageMatcher);
|
||||
} catch(e, trace) {
|
||||
fail('ToolExit expected, got $e\n$trace');
|
||||
}
|
||||
}
|
||||
|
||||
void expectExists(FileSystemEntity entity) {
|
||||
expect(entity.existsSync(), isTrue);
|
||||
}
|
||||
|
||||
void expectNotExists(FileSystemEntity entity) {
|
||||
expect(entity.existsSync(), isFalse);
|
||||
}
|
||||
|
||||
void addIosWithBundleId(Directory directory, String id) {
|
||||
directory
|
||||
.childDirectory('ios')
|
||||
|
Loading…
x
Reference in New Issue
Block a user