From dd2756c36a89b376235072cd4c926eabd6d7aeb9 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 27 Feb 2020 09:45:22 -0800 Subject: [PATCH] Generate a makefile for Linux plugins (#51520) When generating the plugin registrant for Linux, also generate a makefile that can be included in the app-level Makefile to manage all of the plugin targets and flags, exporting them in a few known variables for use in the outer makefile. Part of #32720 --- packages/flutter_tools/lib/src/plugins.dart | 60 ++++++++++++++++++- packages/flutter_tools/lib/src/project.dart | 3 + .../test/general.shard/plugins_test.dart | 53 +++++++++++++++- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index e830fee286..a0dd1f5c68 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -801,6 +801,40 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { } '''; +const String _linuxPluginMakefileTemplate = ''' +# Plugins to include in the build. +GENERATED_PLUGINS=\\ +{{#plugins}} +\t{{name}} \\ +{{/plugins}} + +GENERATED_PLUGINS_DIR={{pluginsDir}} +# A plugin library name plugin name with _plugin appended. +GENERATED_PLUGIN_LIB_NAMES=\$(foreach plugin,\$(GENERATED_PLUGINS),\$(plugin)_plugin) + +# Variables for use in the enclosing Makefile. Changes to these names are +# breaking changes. +PLUGIN_TARGETS=\$(GENERATED_PLUGINS) +PLUGIN_LIBRARIES=\$(foreach plugin,\$(GENERATED_PLUGIN_LIB_NAMES),\\ +\t\$(OUT_DIR)/lib\$(plugin).so) +PLUGIN_LDFLAGS=\$(patsubst %,-l%,\$(GENERATED_PLUGIN_LIB_NAMES)) +PLUGIN_CPPFLAGS=\$(foreach plugin,\$(GENERATED_PLUGINS),\\ +\t-I\$(GENERATED_PLUGINS_DIR)/\$(plugin)/linux) + +# Targets + +# Implicit rules don't match phony targets, so list plugin builds explicitly. +{{#plugins}} +\$(OUT_DIR)/lib{{name}}_plugin.so: | {{name}} +{{/plugins}} + +.PHONY: \$(GENERATED_PLUGINS) +\$(GENERATED_PLUGINS): + make -C \$(GENERATED_PLUGINS_DIR)/\$@/linux \\ + OUT_DIR=\$(OUT_DIR) \\ + FLUTTER_EPHEMERAL_DIR="\$(abspath {{ephemeralDir}})" +'''; + Future _writeIOSPluginRegistrant(FlutterProject project, List plugins) async { final List> iosPlugins = _extractPlatformMaps(plugins, IOSPlugin.kConfigKey); final Map context = { @@ -841,12 +875,34 @@ Future _writeIOSPluginRegistrant(FlutterProject project, List plug } } -Future _writeLinuxPluginRegistrant(FlutterProject project, List plugins) async { +Future _writeLinuxPluginFiles(FlutterProject project, List plugins) async { final List> linuxPlugins = _extractPlatformMaps(plugins, LinuxPlugin.kConfigKey); + // The generated makefile is checked in, so can't use absolute paths. It is + // included by the main makefile, so relative paths must be relative to that + // file's directory. + final String makefileDirPath = project.linux.makeFile.parent.absolute.path; final Map context = { 'plugins': linuxPlugins, + 'ephemeralDir': globals.fs.path.relative( + project.linux.ephemeralDirectory.absolute.path, + from: makefileDirPath, + ), + 'pluginsDir': globals.fs.path.relative( + project.linux.pluginSymlinkDirectory.absolute.path, + from: makefileDirPath, + ), }; await _writeCppPluginRegistrant(project.linux.managedDirectory, context); + await _writeLinuxPluginMakefile(project.linux.managedDirectory, context); +} + +Future _writeLinuxPluginMakefile(Directory destination, Map templateContext) async { + final String registryDirectory = destination.path; + _renderTemplateToFile( + _linuxPluginMakefileTemplate, + templateContext, + globals.fs.path.join(registryDirectory, 'generated_plugins.mk'), + ); } Future _writeMacOSPluginRegistrant(FlutterProject project, List plugins) async { @@ -1034,7 +1090,7 @@ Future injectPlugins(FlutterProject project, {bool checkProjects = false}) // desktop in existing projects are in place. For now, ignore checkProjects // on desktop and always treat it as true. if (featureFlags.isLinuxEnabled && project.linux.existsSync()) { - await _writeLinuxPluginRegistrant(project, plugins); + await _writeLinuxPluginFiles(project, plugins); } if (featureFlags.isMacOSEnabled && project.macos.existsSync()) { await _writeMacOSPluginRegistrant(project, plugins); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 06ca2d695a..4a9944ee59 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -1003,6 +1003,9 @@ class LinuxProject extends FlutterProjectPlatform { /// the build. File get generatedMakeConfigFile => ephemeralDirectory.childFile('generated_config.mk'); + /// Makefile with rules and variables for plugin builds. + File get generatedPluginMakeFile => managedDirectory.childFile('generated_plugins.mk'); + /// The directory to write plugin symlinks. Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks'); diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 7fb5e8775b..50bb3db00c 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -83,7 +83,13 @@ void main() { linuxProject = MockLinuxProject(); when(flutterProject.linux).thenReturn(linuxProject); when(linuxProject.pluginConfigKey).thenReturn('linux'); - when(linuxProject.pluginSymlinkDirectory).thenReturn(flutterProject.directory.childDirectory('linux').childDirectory('symlinks')); + final Directory linuxManagedDirectory = flutterProject.directory.childDirectory('linux').childDirectory('flutter'); + final Directory linuxEphemeralDirectory = linuxManagedDirectory.childDirectory('ephemeral'); + when(linuxProject.managedDirectory).thenReturn(linuxManagedDirectory); + when(linuxProject.ephemeralDirectory).thenReturn(linuxEphemeralDirectory); + when(linuxProject.pluginSymlinkDirectory).thenReturn(linuxEphemeralDirectory.childDirectory('.plugin_symlinks')); + when(linuxProject.makeFile).thenReturn(linuxManagedDirectory.parent.childFile('Makefile')); + when(linuxProject.generatedPluginMakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk')); when(linuxProject.existsSync()).thenReturn(false); when(mockClock.now()).thenAnswer( @@ -871,6 +877,51 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr FeatureFlags: () => featureFlags, }); + testUsingContext('Injecting creates generated Linux registrant', () async { + when(linuxProject.existsSync()).thenReturn(true); + when(featureFlags.isLinuxEnabled).thenReturn(true); + when(flutterProject.isModule).thenReturn(false); + configureDummyPackageAsPlugin(); + + await injectPlugins(flutterProject, checkProjects: true); + + final File registrantHeader = linuxProject.managedDirectory.childFile('generated_plugin_registrant.h'); + final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc'); + + expect(registrantHeader.existsSync(), isTrue); + expect(registrantImpl.existsSync(), isTrue); + expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + }); + + testUsingContext('Injecting creates generated Linux plugin makefile', () async { + when(linuxProject.existsSync()).thenReturn(true); + when(featureFlags.isLinuxEnabled).thenReturn(true); + when(flutterProject.isModule).thenReturn(false); + configureDummyPackageAsPlugin(); + + await injectPlugins(flutterProject, checkProjects: true); + + final File pluginMakefile = linuxProject.generatedPluginMakeFile; + + expect(pluginMakefile.existsSync(), isTrue); + final String contents = pluginMakefile.readAsStringSync(); + expect(contents, contains('libapackage_plugin.so')); + // Verify all the variables the app-level Makefile rely on. + expect(contents, contains('PLUGIN_TARGETS=')); + expect(contents, contains('PLUGIN_LIBRARIES=')); + expect(contents, contains('PLUGIN_LDFLAGS=')); + expect(contents, contains('PLUGIN_CPPFLAGS=')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + }); + + testUsingContext('Injecting creates generated Windows registrant', () async { when(windowsProject.existsSync()).thenReturn(true); when(featureFlags.isWindowsEnabled).thenReturn(true);