Unverified Commit 61c198e7 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Switch Linux to the GTK embedding (#59287)

Updates the tooling to use the GTK embedding, rather than the GLFW embedding:
- Adds new requirements to `doctor`
- Updates the app and plugin templates to make GTK-based runners and plugins
- Stops downloading and installing the GLFW artifacts

Final part of #54860, other than cleanup.
parent 5b147520
......@@ -50,8 +50,6 @@ enum Artifact {
linuxDesktopPath,
// The root of the cpp headers for Linux desktop.
linuxHeaders,
// The root of the cpp client code for Linux desktop.
linuxCppClientWrapper,
/// The root of the Windows desktop sources.
windowsDesktopPath,
/// The root of the cpp client code for Windows desktop.
......@@ -125,8 +123,6 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
return '';
case Artifact.windowsCppClientWrapper:
return 'cpp_client_wrapper';
case Artifact.linuxCppClientWrapper:
return 'cpp_client_wrapper_glfw';
case Artifact.skyEnginePath:
return 'sky_engine';
case Artifact.flutterMacOSPodspec:
......@@ -370,9 +366,6 @@ class CachedArtifacts extends Artifacts {
case Artifact.windowsCppClientWrapper:
final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path;
return _fileSystem.path.join(engineArtifactsPath, 'windows-x64', _artifactToFileName(artifact, platform, mode));
case Artifact.linuxCppClientWrapper:
final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path;
return _fileSystem.path.join(engineArtifactsPath, 'linux-x64', _artifactToFileName(artifact, platform, mode));
case Artifact.skyEnginePath:
final Directory dartPackageDirectory = _cache.getCacheDir('pkg');
return _fileSystem.path.join(dartPackageDirectory.path, _artifactToFileName(artifact));
......@@ -534,8 +527,6 @@ class LocalEngineArtifacts extends Artifacts {
case Artifact.linuxDesktopPath:
case Artifact.linuxHeaders:
return _fileSystem.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.linuxCppClientWrapper:
return _fileSystem.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.windowsDesktopPath:
return _fileSystem.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.windowsCppClientWrapper:
......
......@@ -231,6 +231,13 @@ class UserMessages {
'It is likely available from your distribution (e.g.: apt install ninja-build), or '
'can be downloaded from https://github.com/ninja-build/ninja/releases';
String ninjaTooOld(String minimumVersion) => 'ninja $minimumVersion or later is required.';
String pkgConfigVersion(String version) => 'pkg-config version $version';
String get pkgConfigMissing => 'pgk-config is required for Linux development.\n'
'It is likely available from your distribution (e.g.: apt install pkg-config), or '
'can be downloaded from https://www.freedesktop.org/wiki/Software/pkg-config/';
String pkgConfigTooOld(String minimumVersion) => 'pkg-config $minimumVersion or later is required.';
String get gtkLibrariesMissing => 'GTK 3.0 development libraries are required for Linux development.\n'
'They are likely available from your distribution (e.g.: apt install libgtk-3-dev)';
// Messages used in FlutterCommand
String flutterElapsedTime(String name, String elapsedTime) => '"flutter $name" took $elapsedTime.';
......
......@@ -15,13 +15,6 @@ import 'icon_tree_shaker.dart';
/// The only files/subdirectories we care out.
const List<String> _kLinuxArtifacts = <String>[
// GLFW. Will be removed after the switch to GTK.
'libflutter_linux_glfw.so',
'flutter_export.h',
'flutter_messenger.h',
'flutter_plugin_registrar.h',
'flutter_glfw.h',
// GTK. Not yet used by the template.
'libflutter_linux_gtk.so',
];
......@@ -57,14 +50,6 @@ class UnpackLinux extends Target {
mode: buildMode,
platform: TargetPlatform.linux_x64,
);
// For the GLFW embedding.
final String clientSourcePath = environment.artifacts
.getArtifactPath(
Artifact.linuxCppClientWrapper,
mode: buildMode,
platform: TargetPlatform.linux_x64,
);
// For the GTK embedding.
final String headersPath = environment.artifacts
.getArtifactPath(
Artifact.linuxHeaders,
......@@ -83,7 +68,7 @@ class UnpackLinux extends Target {
engineSourcePath: engineSourcePath,
outputDirectory: outputDirectory,
artifacts: _kLinuxArtifacts,
clientSourcePaths: <String>[clientSourcePath, headersPath],
clientSourcePaths: <String>[headersPath],
icuDataPath: environment.artifacts.getArtifactPath(
Artifact.icuData,
platform: TargetPlatform.linux_x64,
......
......@@ -1400,10 +1400,6 @@ const List<List<String>> _windowsDesktopBinaryDirs = <List<String>>[
];
const List<List<String>> _linuxDesktopBinaryDirs = <List<String>>[
<String>['linux-x64', 'linux-x64/linux-x64-flutter-glfw.zip'],
<String>['linux-x64', 'linux-x64/flutter-cpp-client-wrapper-glfw.zip'],
<String>['linux-x64-profile', 'linux-x64-profile/linux-x64-flutter-glfw.zip'],
<String>['linux-x64-release', 'linux-x64-release/linux-x64-flutter-glfw.zip'],
<String>['linux-x64', 'linux-x64/linux-x64-flutter-gtk.zip'],
<String>['linux-x64-profile', 'linux-x64-profile/linux-x64-flutter-gtk.zip'],
<String>['linux-x64-release', 'linux-x64-release/linux-x64-flutter-gtk.zip'],
......
......@@ -590,6 +590,8 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
final String pluginClass = pluginDartClass.endsWith('Plugin')
? pluginDartClass
: pluginDartClass + 'Plugin';
final String pluginClassSnakeCase = snakeCase(pluginClass);
final String pluginClassCapitalSnakeCase = pluginClassSnakeCase.toUpperCase();
final String appleIdentifier = _createUTIIdentifier(organization, projectName);
return <String, dynamic>{
......@@ -605,8 +607,9 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'androidSdkVersion': android_sdk.minimumAndroidSdkVersion,
'withDriverTest': renderDriverTest,
'pluginClass': pluginClass,
'pluginClassSnakeCase': pluginClassSnakeCase,
'pluginClassCapitalSnakeCase': pluginClassCapitalSnakeCase,
'pluginDartClass': pluginDartClass,
'pluginCppHeaderGuard': projectName.toUpperCase(),
'pluginProjectUUID': Uuid().v4().toUpperCase(),
'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage,
......
......@@ -17,7 +17,7 @@ class _VersionInfo {
/// This should contain a version number. For example:
/// "clang version 9.0.1-6+build1"
_VersionInfo(this.description) {
final String versionString = RegExp(r'[0-9]+\.[0-9]+\.[0-9]+').firstMatch(description).group(0);
final String versionString = RegExp(r'[0-9]+\.[0-9]+(?:\.[0-9]+)?').firstMatch(description).group(0);
number = Version.parse(versionString);
}
......@@ -43,13 +43,21 @@ class LinuxDoctorValidator extends DoctorValidator {
static const String kClangBinary = 'clang++';
static const String kCmakeBinary = 'cmake';
static const String kNinjaBinary = 'ninja';
static const String kPkgConfigBinary = 'pkg-config';
final Map<String, Version> _requiredBinaryVersions = <String, Version>{
kClangBinary: Version(3, 4, 0),
kCmakeBinary: Version(3, 10, 0),
kNinjaBinary: Version(1, 8, 0),
kPkgConfigBinary: Version(0, 29, 0),
};
final List<String> _requiredLibraries = <String>[
'gtk+-3.0',
'glib-2.0',
'gio-2.0',
];
@override
Future<ValidationResult> validate() async {
ValidationType validationType = ValidationType.installed;
......@@ -103,7 +111,7 @@ class LinuxDoctorValidator extends DoctorValidator {
if (version == null) {
messages.add(ValidationMessage.error(_userMessages.ninjaMissing));
} else {
// The full version description is just the number, so context.
// The full version description is just the number, so add context.
messages.add(ValidationMessage(_userMessages.ninjaVersion(version.description)));
final Version requiredVersion = _requiredBinaryVersions[kNinjaBinary];
if (version.number < requiredVersion) {
......@@ -112,6 +120,36 @@ class LinuxDoctorValidator extends DoctorValidator {
}
}
// Message for pkg-config.
{
final _VersionInfo version = installedVersions[kPkgConfigBinary];
if (version == null) {
messages.add(ValidationMessage.error(_userMessages.pkgConfigMissing));
} else {
// The full version description is just the number, so add context.
messages.add(ValidationMessage(_userMessages.pkgConfigVersion(version.description)));
final Version requiredVersion = _requiredBinaryVersions[kPkgConfigBinary];
if (version.number < requiredVersion) {
messages.add(ValidationMessage.error(_userMessages.pkgConfigTooOld(requiredVersion.toString())));
}
}
}
// Message for libraries.
{
bool libraryMissing = false;
for (final String library in _requiredLibraries) {
if (!await _libraryIsPresent(library)) {
libraryMissing = true;
break;
}
}
if (libraryMissing) {
validationType = ValidationType.missing;
messages.add(ValidationMessage.error(_userMessages.gtkLibrariesMissing));
}
}
return ValidationResult(validationType, messages);
}
......@@ -135,4 +173,19 @@ class LinuxDoctorValidator extends DoctorValidator {
final String firstLine = (result.stdout as String).split('\n').first.trim();
return _VersionInfo(firstLine);
}
/// Checks that [library] is available via pkg-config.
Future<bool> _libraryIsPresent(String library) async {
ProcessResult result;
try {
result = await _processManager.run(<String>[
'pkg-config',
'--exists',
library,
]);
} on ArgumentError {
// ignore error.
}
return (result?.exitCode ?? 1) == 0;
}
}
......@@ -793,7 +793,47 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
}
''';
const String _linuxPluginRegistryHeaderTemplate = '''
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
''';
const String _linuxPluginRegistryImplementationTemplate = '''
//
// Generated file. Do not edit.
//
#include "generated_plugin_registrant.h"
{{#plugins}}
#include <{{name}}/{{filename}}.h>
{{/plugins}}
void fl_register_plugins(FlPluginRegistry* registry) {
{{#plugins}}
g_autoptr(FlPluginRegistrar) {{name}}_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "{{class}}");
{{filename}}_register_with_registrar({{name}}_registrar);
{{/plugins}}
}
''';
const String _linuxPluginCmakefileTemplate = r'''
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
{{#plugins}}
{{name}}
......@@ -864,10 +904,24 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins
from: makefileDirPath,
),
};
await _writeCppPluginRegistrant(project.linux.managedDirectory, context);
await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
await _writeLinuxPluginCmakefile(project.linux.generatedPluginCmakeFile, context);
}
Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
final String registryDirectory = destination.path;
_renderTemplateToFile(
_linuxPluginRegistryHeaderTemplate,
templateContext,
globals.fs.path.join(registryDirectory, 'generated_plugin_registrant.h'),
);
_renderTemplateToFile(
_linuxPluginRegistryImplementationTemplate,
templateContext,
globals.fs.path.join(registryDirectory, 'generated_plugin_registrant.cc'),
);
}
Future<void> _writeLinuxPluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async {
_renderTemplateToFile(
_linuxPluginCmakefileTemplate,
......
......@@ -28,14 +28,20 @@ set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"window_configuration.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
add_dependencies(${BINARY_NAME} flutter_assemble)
# Generated plugin build rules, which manage building the plugins and adding
......
......@@ -7,7 +7,6 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper_glfw")
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
......@@ -20,7 +19,13 @@ function(list_prepend LIST_NAME PREFIX)
endfunction()
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_glfw.so")
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
......@@ -29,62 +34,39 @@ set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_glfw.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/")
list_prepend(FLUTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"engine_method_result.cc"
"standard_codec.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_CORE "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_PLUGIN "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_window_controller.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_APP "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter_wrapper_app flutter_assemble)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
......@@ -92,8 +74,6 @@ add_dependencies(flutter_wrapper_app flutter_assemble)
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
......@@ -103,7 +83,4 @@ add_custom_command(
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
#include <flutter/flutter_engine.h>
#include <flutter/flutter_window_controller.h>
#include <linux/limits.h>
#include <unistd.h>
#include "my_application.h"
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>
#include "flutter/generated_plugin_registrant.h"
#include "window_configuration.h"
namespace {
// Runs the application in headless mode, without a window.
void RunHeadless(const std::string& icu_data_path,
const std::string& assets_path,
const std::vector<std::string>& arguments,
const std::string& aot_library_path) {
flutter::FlutterEngine engine;
engine.Start(icu_data_path, assets_path, arguments, aot_library_path);
RegisterPlugins(&engine);
while (true) {
engine.RunEventLoopWithTimeout();
}
}
} // namespace
int main(int argc, char **argv) {
std::string data_directory = "data";
std::string assets_path = data_directory + "/flutter_assets";
std::string icu_data_path = data_directory + "/icudtl.dat";
std::string lib_directory = "lib";
std::string aot_library_path = lib_directory + "/libapp.so";
// Arguments for the Flutter Engine.
std::vector<std::string> arguments;
flutter::FlutterWindowController flutter_controller(icu_data_path);
flutter::WindowProperties window_properties = {};
window_properties.title = kFlutterWindowTitle;
window_properties.width = kFlutterWindowWidth;
window_properties.height = kFlutterWindowHeight;
// Start the engine.
if (!flutter_controller.CreateWindow(window_properties, assets_path,
arguments, aot_library_path)) {
if (getenv("DISPLAY") == nullptr) {
std::cout << "No DISPLAY; falling back to headless mode." << std::endl;
RunHeadless(icu_data_path, assets_path, arguments, aot_library_path);
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}
RegisterPlugins(&flutter_controller);
// Run until the window is closed.
while (flutter_controller.RunEventLoopWithTimeout()) {
}
return EXIT_SUCCESS;
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#include "flutter/generated_plugin_registrant.h"
#include "window_configuration.h"
struct _MyApplication {
GtkApplication parent_instance;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
gtk_widget_show(GTK_WIDGET(window));
gtk_widget_set_size_request(GTK_WIDGET(window), kFlutterWindowWidth,
kFlutterWindowHeight);
gtk_window_set_title(window, kFlutterWindowTitle);
g_autoptr(FlDartProject) project = fl_dart_project_new();
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr));
}
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
#ifndef WINDOW_CONFIGURATION_
#define WINDOW_CONFIGURATION_
// This is a temporary approach to isolate common customizations from main.cpp,
// This is a temporary approach to isolate common customizations from main.cc,
// where the APIs are still in flux. This should simplify re-creating the
// runner while preserving local changes.
//
......
......@@ -13,7 +13,8 @@ set_target_properties(${PLUGIN_NAME} PROPERTIES
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
# List of absolute paths to libraries that should be bundled with the plugin
set({{projectName}}_bundled_libraries
......
#ifndef FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#define FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#include <flutter_linux/flutter_linux.h>
G_BEGIN_DECLS
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define FLUTTER_PLUGIN_EXPORT
#endif
typedef struct _{{pluginClass}} {{pluginClass}};
typedef struct {
GObjectClass parent_class;
} {{pluginClass}}Class;
FLUTTER_PLUGIN_EXPORT GType {{pluginClassSnakeCase}}_get_type();
FLUTTER_PLUGIN_EXPORT void {{pluginClassSnakeCase}}_register_with_registrar(
FlPluginRegistrar* registrar);
G_END_DECLS
#endif // FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#ifndef FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#define FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define FLUTTER_PLUGIN_EXPORT
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void {{pluginClass}}RegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#include "include/{{projectName}}_plugin.h"
#include "include/{{projectName}}/{{projectName}}_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_glfw.h>
#include <flutter/standard_method_codec.h>
#include <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
#include <sys/utsname.h>
#include <map>
#include <memory>
#include <sstream>
#define {{pluginClassCapitalSnakeCase}}(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), {{pluginClassSnakeCase}}_get_type(), \
{{pluginClass}}))
namespace {
class {{pluginClass}} : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarGlfw *registrar);
struct _{{pluginClass}} {
GObject parent_instance;
};
{{pluginClass}}();
G_DEFINE_TYPE({{pluginClass}}, {{pluginClassSnakeCase}}, g_object_get_type())
virtual ~{{pluginClass}}();
// Called when a method call is received from Flutter.
static void {{pluginClassSnakeCase}}_handle_method_call(
{{pluginClass}}* self,
FlMethodCall* method_call) {
g_autoptr(FlMethodResponse) response = nullptr;
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
};
const gchar* method = fl_method_call_get_name(method_call);
// static
void {{pluginClass}}::RegisterWithRegistrar(
flutter::PluginRegistrarGlfw *registrar) {
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "{{projectName}}",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<{{pluginClass}}>();
if (strcmp(method, "getPlatformVersion") == 0) {
struct utsname uname_data = {};
uname(&uname_data);
g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
g_autoptr(FlValue) result = fl_value_new_string(version);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
fl_method_call_respond(method_call, response, nullptr);
}
registrar->AddPlugin(std::move(plugin));
static void {{pluginClassSnakeCase}}_dispose(GObject* object) {
G_OBJECT_CLASS({{pluginClassSnakeCase}}_parent_class)->dispose(object);
}
{{pluginClass}}::{{pluginClass}}() {}
static void {{pluginClassSnakeCase}}_class_init({{pluginClass}}Class* klass) {
G_OBJECT_CLASS(klass)->dispose = {{pluginClassSnakeCase}}_dispose;
}
{{pluginClass}}::~{{pluginClass}}() {}
static void {{pluginClassSnakeCase}}_init({{pluginClass}}* self) {}
void {{pluginClass}}::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
// Replace "getPlatformVersion" check with your plugin's method.
// See:
// https://github.com/flutter/engine/tree/master/shell/platform/common/cpp/client_wrapper/include/flutter
// and
// https://github.com/flutter/engine/tree/master/shell/platform/glfw/client_wrapper/include/flutter
// for the relevant Flutter APIs.
if (method_call.method_name().compare("getPlatformVersion") == 0) {
struct utsname uname_data = {};
uname(&uname_data);
std::ostringstream version_stream;
version_stream << "Linux " << uname_data.version;
flutter::EncodableValue response(version_stream.str());
result->Success(&response);
} else {
result->NotImplemented();
}
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
gpointer user_data) {
{{pluginClass}}* plugin = {{pluginClassCapitalSnakeCase}}(user_data);
{{pluginClassSnakeCase}}_handle_method_call(plugin, method_call);
}
} // namespace
void {{pluginClassSnakeCase}}_register_with_registrar(FlPluginRegistrar* registrar) {
{{pluginClass}}* plugin = {{pluginClassCapitalSnakeCase}}(
g_object_new({{pluginClassSnakeCase}}_get_type(), nullptr));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlMethodChannel) channel =
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"{{projectName}}",
FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(channel, method_call_cb,
g_object_ref(plugin),
g_object_unref);
void {{pluginClass}}RegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
{{pluginClass}}::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarGlfw>(registrar));
g_object_unref(plugin);
}
#ifndef FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#define FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#ifndef FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#defineFLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#include <flutter_plugin_registrar.h>
......@@ -20,4 +20,4 @@ FLUTTER_PLUGIN_EXPORT void {{pluginClass}}RegisterWithRegistrar(
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_{{pluginCppHeaderGuard}}_PLUGIN_H_
#endif // FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
......@@ -28,11 +28,6 @@ void main() {
mode: anyNamed('mode'),
platform: anyNamed('platform'),
)).thenReturn('linux-x64');
when(mockArtifacts.getArtifactPath(
Artifact.linuxCppClientWrapper,
mode: anyNamed('mode'),
platform: anyNamed('platform'),
)).thenReturn('linux-x64/cpp_client_wrapper_glfw');
when(mockArtifacts.getArtifactPath(
Artifact.linuxHeaders,
mode: anyNamed('mode'),
......@@ -58,17 +53,8 @@ void main() {
await const UnpackLinux().build(testEnvironment);
// GLFW.
expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_glfw.so'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/flutter_export.h'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/flutter_messenger.h'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/flutter_plugin_registrar.h'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/flutter_glfw.h'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/cpp_client_wrapper_glfw/foo'), exists);
// GTK.
expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_gtk.so'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/flutter_linux/foo.h'), exists);
// Both.
expect(fileSystem.file('linux/flutter/ephemeral/icudtl.dat'), exists);
expect(fileSystem.file('linux/flutter/ephemeral/unrelated-stuff'), isNot(exists));
});
......@@ -195,17 +181,8 @@ void main() {
void setUpCacheDirectory(FileSystem fileSystem) {
fileSystem.file('linux-x64/unrelated-stuff').createSync(recursive: true);
// GLFW.
fileSystem.file('linux-x64/libflutter_linux_glfw.so').createSync(recursive: true);
fileSystem.file('linux-x64/flutter_export.h').createSync();
fileSystem.file('linux-x64/flutter_messenger.h').createSync();
fileSystem.file('linux-x64/flutter_plugin_registrar.h').createSync();
fileSystem.file('linux-x64/flutter_glfw.h').createSync();
fileSystem.file('linux-x64/cpp_client_wrapper_glfw/foo').createSync(recursive: true);
// GTK.
fileSystem.file('linux-x64/libflutter_linux_gtk.so').createSync(recursive: true);
fileSystem.file('linux-x64/flutter_linux/foo.h').createSync(recursive: true);
// Both.
fileSystem.file('linux-x64/icudtl.dat').createSync();
fileSystem.file('packages/flutter_tools/lib/src/build_system/targets/linux.dart').createSync(recursive: true);
}
......
......@@ -46,6 +46,36 @@ FakeCommand _ninjaPresentCommand(String version) {
);
}
// A command that will return typical-looking 'pgk-config --version' output with
// the given version number.
FakeCommand _pkgConfigPresentCommand(String version) {
return FakeCommand(
command: const <String>['pkg-config', '--version'],
stdout: version,
);
}
// Commands that give positive replies for all the library pkg-config queries.
List<FakeCommand> _librariesPresentCommands() {
return const <FakeCommand>[
FakeCommand(command: <String>['pkg-config', '--exists', 'gtk+-3.0']),
FakeCommand(command: <String>['pkg-config', '--exists', 'glib-2.0']),
FakeCommand(command: <String>['pkg-config', '--exists', 'gio-2.0']),
];
}
// Commands that give some failures for the library pkg-config queries.
List<FakeCommand> _librariesMissingCommands() {
return const <FakeCommand>[
FakeCommand(command: <String>['pkg-config', '--exists', 'gtk+-3.0']),
FakeCommand(
command: <String>['pkg-config', '--exists', 'glib-2.0'],
exitCode: 1,
),
FakeCommand(command: <String>['pkg-config', '--exists', 'gio-2.0']),
];
}
// A command that will failure when running '[binary] --version'.
FakeCommand _missingBinaryCommand(String binary) {
return FakeCommand(
......@@ -60,6 +90,8 @@ void main() {
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
......@@ -72,6 +104,7 @@ void main() {
ValidationMessage('clang version 4.0.1-6+build1'),
ValidationMessage('cmake version 3.16.3'),
ValidationMessage('ninja version 1.10.0'),
ValidationMessage('pkg-config version 0.29'),
]);
});
......@@ -80,6 +113,8 @@ void main() {
_clangPresentCommand('2.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
......@@ -93,6 +128,7 @@ void main() {
ValidationMessage.error('clang++ 3.4.0 or later is required.'),
ValidationMessage('cmake version 3.16.3'),
ValidationMessage('ninja version 1.10.0'),
ValidationMessage('pkg-config version 0.29'),
]);
});
......@@ -101,6 +137,8 @@ void main() {
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.2.0'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
......@@ -114,6 +152,7 @@ void main() {
ValidationMessage('cmake version 3.2.0'),
ValidationMessage.error('cmake 3.10.0 or later is required.'),
ValidationMessage('ninja version 1.10.0'),
ValidationMessage('pkg-config version 0.29'),
]);
});
......@@ -122,6 +161,8 @@ void main() {
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('0.8.1'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
......@@ -135,6 +176,31 @@ void main() {
ValidationMessage('cmake version 3.16.3'),
ValidationMessage('ninja version 0.8.1'),
ValidationMessage.error('ninja 1.8.0 or later is required.'),
ValidationMessage('pkg-config version 0.29'),
]);
});
testWithoutContext('Partial validation when pkg-config version is too old', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.27.0'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
userMessages: UserMessages(),
);
final ValidationResult result = await linuxDoctorValidator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages, const <ValidationMessage>[
ValidationMessage('clang version 4.0.1-6+build1'),
ValidationMessage('cmake version 3.16.3'),
ValidationMessage('ninja version 1.10.0'),
ValidationMessage('pkg-config version 0.27.0'),
ValidationMessage.error('pkg-config 0.29.0 or later is required.'),
]);
});
......@@ -143,6 +209,8 @@ void main() {
_clangPresentCommand('4.0.1'),
_missingBinaryCommand('cmake'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final UserMessages userMessages = UserMessages();
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
......@@ -156,6 +224,7 @@ void main() {
const ValidationMessage('clang version 4.0.1-6+build1'),
ValidationMessage.error(userMessages.cmakeMissing),
const ValidationMessage('ninja version 1.10.0'),
const ValidationMessage('pkg-config version 0.29'),
]);
});
......@@ -164,6 +233,8 @@ void main() {
_missingBinaryCommand('clang++'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final UserMessages userMessages = UserMessages();
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
......@@ -177,6 +248,7 @@ void main() {
ValidationMessage.error(userMessages.clangMissing),
const ValidationMessage('cmake version 3.16.3'),
const ValidationMessage('ninja version 1.10.0'),
const ValidationMessage('pkg-config version 0.29'),
]);
});
......@@ -185,6 +257,8 @@ void main() {
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_missingBinaryCommand('ninja'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final UserMessages userMessages = UserMessages();
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
......@@ -198,6 +272,56 @@ void main() {
const ValidationMessage('clang version 4.0.1-6+build1'),
const ValidationMessage('cmake version 3.16.3'),
ValidationMessage.error(userMessages.ninjaMissing),
const ValidationMessage('pkg-config version 0.29'),
]);
});
testWithoutContext('Missing validation when pkg-config is not available', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_missingBinaryCommand('pkg-config'),
..._librariesPresentCommands(),
]);
final UserMessages userMessages = UserMessages();
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
userMessages: userMessages,
);
final ValidationResult result = await linuxDoctorValidator.validate();
expect(result.type, ValidationType.missing);
expect(result.messages, <ValidationMessage>[
const ValidationMessage('clang version 4.0.1-6+build1'),
const ValidationMessage('cmake version 3.16.3'),
const ValidationMessage('ninja version 1.10.0'),
ValidationMessage.error(userMessages.pkgConfigMissing),
]);
});
testWithoutContext('Missing validation when libraries are not available', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
_clangPresentCommand('4.0.1'),
_cmakePresentCommand('3.16.3'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesMissingCommands(),
]);
final UserMessages userMessages = UserMessages();
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
userMessages: userMessages,
);
final ValidationResult result = await linuxDoctorValidator.validate();
expect(result.type, ValidationType.missing);
expect(result.messages, <ValidationMessage>[
const ValidationMessage('clang version 4.0.1-6+build1'),
const ValidationMessage('cmake version 3.16.3'),
const ValidationMessage('ninja version 1.10.0'),
const ValidationMessage('pkg-config version 0.29'),
ValidationMessage.error(userMessages.gtkLibrariesMissing),
]);
});
......@@ -206,6 +330,8 @@ void main() {
_missingBinaryCommand('clang++'),
_missingBinaryCommand('cmake'),
_ninjaPresentCommand('1.10.0'),
_pkgConfigPresentCommand('0.29'),
..._librariesPresentCommands(),
]);
final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
processManager: processManager,
......
......@@ -956,7 +956,7 @@ flutter:
expect(registrantHeader.existsSync(), isTrue);
expect(registrantImpl.existsSync(), isTrue);
expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar'));
expect(registrantImpl.readAsStringSync(), contains('some_plugin_register_with_registrar'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
......@@ -984,6 +984,7 @@ flutter:
expect(registrantImpl, exists);
expect(registrantImpl, isNot(contains('SomePlugin')));
expect(registrantImpl, isNot(contains('some_plugin')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment