diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 4458339482e..2d01a9e7cde 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -167,6 +167,9 @@ Future run(List arguments) async { printProgress('Lint Kotlin files...'); await lintKotlinFiles(flutterRoot); + printProgress('Lint generated Kotlin files from templates...'); + await lintKotlinTemplatedFiles(flutterRoot); + // Ensure that all package dependencies are in sync. printProgress('Package dependencies...'); await runCommand(flutter, [ @@ -2460,6 +2463,66 @@ Future verifyTabooDocumentation(String workingDirectory, {int minimumMatch } } +final Map _kKotlinTemplateKeys = { + 'androidIdentifier': 'dummyPackage', + 'pluginClass': 'PluginClass', + 'projectName': 'dummy', + 'agpVersion': '0.0.0.1', + 'kotlinVersion': '0.0.0.1', +}; + +final String _kKotlinTemplateRelativePath = path.join('packages', 'flutter_tools', 'templates'); + +const List _kKotlinExtList = ['.kt.tmpl', '.kts.tmpl']; +const String _kKotlinTmplExt = '.tmpl'; +final RegExp _kKotlinTemplatePattern = RegExp(r'{{(.*?)}}'); + +/// Copy kotlin template files from [_kKotlinTemplateRelativePath] into a system tmp folder +/// then replace template values with values from [_kKotlinTemplateKeys] or "'dummy'" if an +/// unknown key is found. Then run ktlint on the tmp folder to check for lint errors in the +/// generated Kotlin files. +Future lintKotlinTemplatedFiles(String workingDirectory) async { + final String templatePath = path.join(workingDirectory, _kKotlinTemplateRelativePath); + final Iterable files = Directory(templatePath) + .listSync(recursive: true) + .toList() + .whereType() + .where((File file) => _kKotlinExtList.contains(path.extension(file.path, 2))); + + if (files.isEmpty) { + foundError(['No Kotlin template files found']); + return; + } + + final Directory tempDir = Directory.systemTemp.createTempSync('template_output'); + for (final File templateFile in files) { + final String inputContent = await templateFile.readAsString(); + final String modifiedContent = inputContent.replaceAllMapped( + _kKotlinTemplatePattern, + (Match match) => _kKotlinTemplateKeys[match[1]] ?? 'dummy', + ); + + String outputFilename = path.basename(templateFile.path); + outputFilename = outputFilename.substring( + 0, + outputFilename.length - _kKotlinTmplExt.length, + ); // Remove '.tmpl' from file path + + // Ensure the first letter of the generated class is uppercase (instead of pluginClass) + outputFilename = outputFilename.substring(0, 1).toUpperCase() + outputFilename.substring(1); + + final String relativePath = path.dirname(path.relative(templateFile.path, from: templatePath)); + final String outputDir = path.join(tempDir.path, relativePath); + await Directory(outputDir).create(recursive: true); + final String outputFile = path.join(outputDir, outputFilename); + final File output = File(outputFile); + await output.writeAsString(modifiedContent); + } + return lintKotlinFiles(tempDir.path).whenComplete(() { + tempDir.deleteSync(recursive: true); + }); +} + Future lintKotlinFiles(String workingDirectory) async { const String baselineRelativePath = 'dev/bots/test/analyze-test-input/ktlint-baseline.xml'; const String editorConfigRelativePath = 'dev/bots/test/analyze-test-input/.editorconfig'; diff --git a/packages/flutter_tools/templates/app/android-java.tmpl/build.gradle.kts.tmpl b/packages/flutter_tools/templates/app/android-java.tmpl/build.gradle.kts.tmpl index 89176ef44e8..dbee657bb5b 100644 --- a/packages/flutter_tools/templates/app/android-java.tmpl/build.gradle.kts.tmpl +++ b/packages/flutter_tools/templates/app/android-java.tmpl/build.gradle.kts.tmpl @@ -5,7 +5,10 @@ allprojects { } } -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { diff --git a/packages/flutter_tools/templates/app/android-kotlin.tmpl/build.gradle.kts.tmpl b/packages/flutter_tools/templates/app/android-kotlin.tmpl/build.gradle.kts.tmpl index 89176ef44e8..dbee657bb5b 100644 --- a/packages/flutter_tools/templates/app/android-kotlin.tmpl/build.gradle.kts.tmpl +++ b/packages/flutter_tools/templates/app/android-kotlin.tmpl/build.gradle.kts.tmpl @@ -5,7 +5,10 @@ allprojects { } } -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { diff --git a/packages/flutter_tools/templates/app/android.tmpl/settings.gradle.kts.tmpl b/packages/flutter_tools/templates/app/android.tmpl/settings.gradle.kts.tmpl index d0d44f33496..41bd0d50402 100644 --- a/packages/flutter_tools/templates/app/android.tmpl/settings.gradle.kts.tmpl +++ b/packages/flutter_tools/templates/app/android.tmpl/settings.gradle.kts.tmpl @@ -1,11 +1,12 @@ pluginManagement { - val flutterSdkPath = run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl index 9ef25ee7c91..3c6e0f59fc9 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl @@ -7,27 +7,32 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result /** {{pluginClass}} */ -class {{pluginClass}}: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel +class {{pluginClass}} : + FlutterPlugin, + MethodCallHandler { + // The MethodChannel that will the communication between Flutter and native Android + // + // This local reference serves to register the plugin with the Flutter Engine and unregister it + // when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "{{projectName}}") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "{{projectName}}") + channel.setMethodCallHandler(this) } - } - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } + override fun onMethodCall( + call: MethodCall, + result: Result + ) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } } diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl index dd0a1da7b3c..3217dcf9088 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/test/kotlin/androidIdentifier/pluginClassTest.kt.tmpl @@ -2,8 +2,8 @@ package {{androidIdentifier}} import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import kotlin.test.Test import org.mockito.Mockito +import kotlin.test.Test /* * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. @@ -14,14 +14,14 @@ import org.mockito.Mockito */ internal class {{pluginClass}}Test { - @Test - fun onMethodCall_getPlatformVersion_returnsExpectedValue() { - val plugin = {{pluginClass}}() + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = {{pluginClass}}() - val call = MethodCall("getPlatformVersion", null) - val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) - plugin.onMethodCall(call, mockResult) + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) - Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) - } + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } }