diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index dcbc6ff728..4d0244d560 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart @@ -166,6 +166,7 @@ class Template { final String projectName = context['projectName'] as String; final String androidIdentifier = context['androidIdentifier'] as String; final String pluginClass = context['pluginClass'] as String; + final String pluginClassSnakeCase = context['pluginClassSnakeCase'] as String; final String destinationDirPath = destination.absolute.path; final String pathSeparator = _fileSystem.path.separator; String finalDestinationPath = _fileSystem.path @@ -181,6 +182,10 @@ class Template { if (projectName != null) { finalDestinationPath = finalDestinationPath.replaceAll('projectName', projectName); } + // This must be before the pluginClass replacement step. + if (pluginClassSnakeCase != null) { + finalDestinationPath = finalDestinationPath.replaceAll('pluginClassSnakeCase', pluginClassSnakeCase); + } if (pluginClass != null) { finalDestinationPath = finalDestinationPath.replaceAll('pluginClass', pluginClass); } diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl index 9cd8638088..8df454676a 100644 --- a/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl @@ -2,10 +2,12 @@ cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME "{{projectName}}") project(${PROJECT_NAME} LANGUAGES CXX) -set(PLUGIN_NAME "${PROJECT_NAME}_plugin") +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "{{projectName}}_plugin") add_library(${PLUGIN_NAME} SHARED - "${PLUGIN_NAME}.cc" + "{{pluginClassSnakeCase}}.cc" ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl rename to packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl similarity index 97% rename from packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl rename to packages/flutter_tools/templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl index fc7a62aba6..9c53239069 100644 --- a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl +++ b/packages/flutter_tools/templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl @@ -1,4 +1,4 @@ -#include "include/{{projectName}}/{{projectName}}_plugin.h" +#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h" #include #include diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/CMakeLists.txt.tmpl index 1399a828b2..149d3adb9d 100644 --- a/packages/flutter_tools/templates/plugin/windows.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/plugin/windows.tmpl/CMakeLists.txt.tmpl @@ -2,10 +2,12 @@ cmake_minimum_required(VERSION 3.15) set(PROJECT_NAME "{{projectName}}") project(${PROJECT_NAME} LANGUAGES CXX) -set(PLUGIN_NAME "${PROJECT_NAME}_plugin") +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "{{projectName}}_plugin") add_library(${PLUGIN_NAME} SHARED - "${PLUGIN_NAME}.cpp" + "{{pluginClassSnakeCase}}.cpp" ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/windows.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl rename to packages/flutter_tools/templates/plugin/windows.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl diff --git a/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl b/packages/flutter_tools/templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl similarity index 97% rename from packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl rename to packages/flutter_tools/templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl index 0d12805be3..0fcba864d5 100644 --- a/packages/flutter_tools/templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl +++ b/packages/flutter_tools/templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl @@ -1,4 +1,4 @@ -#include "include/{{projectName}}/{{projectName}}_plugin.h" +#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h" // This must be included before many other Windows headers. #include diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index 8d19292ff1..f8a6be6b00 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -263,8 +263,8 @@ "templates/plugin/lib/projectName.dart.tmpl", "templates/plugin/LICENSE.tmpl", "templates/plugin/linux.tmpl/CMakeLists.txt.tmpl", - "templates/plugin/linux.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl", - "templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl", + "templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl", + "templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl", "templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl", "templates/plugin/macos.tmpl/projectName.podspec.tmpl", "templates/plugin/projectName.iml.tmpl", @@ -273,8 +273,8 @@ "templates/plugin/test/projectName_test.dart.tmpl", "templates/plugin/windows.tmpl/.gitignore", "templates/plugin/windows.tmpl/CMakeLists.txt.tmpl", - "templates/plugin/windows.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl", - "templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl", + "templates/plugin/windows.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl", + "templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl", "templates/plugin/lib/projectName_web.dart.tmpl" ] } diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index a22a2a5155..c8b2d031d5 100755 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -2044,6 +2044,148 @@ void main() { expect(buildContent.contains('targetSdkVersion 29'), true); }); + testUsingContext('Linux plugins handle partially camel-case project names correctly', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + const String projectName = 'foo_BarBaz'; + final Directory projectDir = tempDir.childDirectory(projectName); + await runner.run(['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]); + final Directory platformDir = projectDir.childDirectory('linux'); + + const String classFilenameBase = 'foo_bar_baz_plugin'; + const String headerName = '$classFilenameBase.h'; + final File headerFile = platformDir + .childDirectory('include') + .childDirectory(projectName) + .childFile(headerName); + final File implFile = platformDir.childFile('$classFilenameBase.cc'); + // Ensure that the files have the right names. + expect(headerFile.existsSync(), true); + expect(implFile.existsSync(), true); + // Ensure that the include is correct. + expect(implFile.readAsStringSync(), contains(headerName)); + // Ensure that the CMake file has the right target and source values. + final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync(); + expect(cmakeContents, contains('"$classFilenameBase.cc"')); + expect(cmakeContents, contains('set(PLUGIN_NAME "foo_BarBaz_plugin")')); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), + }); + + testUsingContext('Windows plugins handle partially camel-case project names correctly', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + const String projectName = 'foo_BarBaz'; + final Directory projectDir = tempDir.childDirectory(projectName); + await runner.run(['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]); + final Directory platformDir = projectDir.childDirectory('windows'); + + const String classFilenameBase = 'foo_bar_baz_plugin'; + const String headerName = '$classFilenameBase.h'; + final File headerFile = platformDir + .childDirectory('include') + .childDirectory(projectName) + .childFile(headerName); + final File implFile = platformDir.childFile('$classFilenameBase.cpp'); + // Ensure that the files have the right names. + expect(headerFile.existsSync(), true); + expect(implFile.existsSync(), true); + // Ensure that the include is correct. + expect(implFile.readAsStringSync(), contains(headerName)); + // Ensure that the plugin target name matches the post-processed version. + // Ensure that the CMake file has the right target and source values. + final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync(); + expect(cmakeContents, contains('"$classFilenameBase.cpp"')); + expect(cmakeContents, contains('set(PLUGIN_NAME "foo_BarBaz_plugin")')); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); + + testUsingContext('Linux plugins handle project names ending in _plugin correctly', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + const String projectName = 'foo_bar_plugin'; + final Directory projectDir = tempDir.childDirectory(projectName); + await runner.run(['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]); + final Directory platformDir = projectDir.childDirectory('linux'); + + // If the project already ends in _plugin, it shouldn't be added again. + const String classFilenameBase = projectName; + const String headerName = '$classFilenameBase.h'; + final File headerFile = platformDir + .childDirectory('include') + .childDirectory(projectName) + .childFile(headerName); + final File implFile = platformDir.childFile('$classFilenameBase.cc'); + // Ensure that the files have the right names. + expect(headerFile.existsSync(), true); + expect(implFile.existsSync(), true); + // Ensure that the include is correct. + expect(implFile.readAsStringSync(), contains(headerName)); + // Ensure that the CMake file has the right target and source values. + final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync(); + expect(cmakeContents, contains('"$classFilenameBase.cc"')); + // The "_plugin_plugin" suffix is intentional; because the target names must + // be unique across the ecosystem, no canonicalization can be done, + // otherwise plugins called "foo_bar" and "foo_bar_plugin" would collide in + // builds. + expect(cmakeContents, contains('set(PLUGIN_NAME "foo_bar_plugin_plugin")')); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), + }); + + testUsingContext('Windows plugins handle project names ending in _plugin correctly', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + const String projectName = 'foo_bar_plugin'; + final Directory projectDir = tempDir.childDirectory(projectName); + await runner.run(['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]); + final Directory platformDir = projectDir.childDirectory('windows'); + + // If the project already ends in _plugin, it shouldn't be added again. + const String classFilenameBase = projectName; + const String headerName = '$classFilenameBase.h'; + final File headerFile = platformDir + .childDirectory('include') + .childDirectory(projectName) + .childFile(headerName); + final File implFile = platformDir.childFile('$classFilenameBase.cpp'); + // Ensure that the files have the right names. + expect(headerFile.existsSync(), true); + expect(implFile.existsSync(), true); + // Ensure that the include is correct. + expect(implFile.readAsStringSync(), contains(headerName)); + // Ensure that the CMake file has the right target and source values. + final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync(); + expect(cmakeContents, contains('"$classFilenameBase.cpp"')); + // The "_plugin_plugin" suffix is intentional; because the target names must + // be unique across the ecosystem, no canonicalization can be done, + // otherwise plugins called "foo_bar" and "foo_bar_plugin" would collide in + // builds. + expect(cmakeContents, contains('set(PLUGIN_NAME "foo_bar_plugin_plugin")')); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); } Future _createProject(