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
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index e830fee..a0dd1f5 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -801,6 +801,40 @@
}
''';
+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<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> iosPlugins = _extractPlatformMaps(plugins, IOSPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
@@ -841,12 +875,34 @@
}
}
-Future<void> _writeLinuxPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
+Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> 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<String, dynamic> context = <String, dynamic>{
'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<void> _writeLinuxPluginMakefile(Directory destination, Map<String, dynamic> templateContext) async {
+ final String registryDirectory = destination.path;
+ _renderTemplateToFile(
+ _linuxPluginMakefileTemplate,
+ templateContext,
+ globals.fs.path.join(registryDirectory, 'generated_plugins.mk'),
+ );
}
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
@@ -1034,7 +1090,7 @@
// 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 06ca2d6..4a9944e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -1003,6 +1003,9 @@
/// 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 7fb5e87..50bb3db 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 @@
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 @@
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: <Type, Generator>{
+ 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: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => FakeProcessManager.any(),
+ FeatureFlags: () => featureFlags,
+ });
+
+
testUsingContext('Injecting creates generated Windows registrant', () async {
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);