Unverified Commit 8d8439f8 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Add pre-stable support for create on Linux (#51832)

Adds initial support for `flutter create` of apps and plugins. This is derived from the current FDE example app and sample plugin, with a few changes:
- Added template values where it makes sense.
- Moved some likely-to-change values into separate files for now, to simplify the delete/recreate cycle that will be necessary until it's stable.
- Added some minor Makefile flag handling improvements

Since the APIs/tooling/template aren't stable yet, the app template includes a version marker, which will be updated each time there's a breaking change. The build now checks that the template version matches the version known by that version of the tool, and gives a specific error message when there's a mismatch, which improves over the current breaking change experience of hitting whatever build failure the breaking change causes and having to figure out that the problem is that the runner is out of date. It also adds a warning to the `create` output about the fact that it won't be stable.
parent 28f2c96a
......@@ -398,6 +398,7 @@ class CreateCommand extends FlutterCommand {
androidLanguage: stringArg('android-language'),
iosLanguage: stringArg('ios-language'),
web: featureFlags.isWebEnabled,
linux: featureFlags.isLinuxEnabled,
macos: featureFlags.isMacOSEnabled,
);
......@@ -490,6 +491,15 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'directory in order to launch your app.');
globals.printStatus('Your $application code is in $relativeAppMain');
}
// Warn about unstable templates. This shuold be last so that it's not
// lost among the other output.
if (featureFlags.isLinuxEnabled) {
globals.printStatus('');
globals.printStatus('WARNING: The Linux tooling and APIs are not yet stable. '
'You will likely need to re-create the "linux" directory after future '
'Flutter updates.');
}
}
return FlutterCommandResult.success();
}
......@@ -608,6 +618,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
bool renderDriverTest = false,
bool withPluginHook = false,
bool web = false,
bool linux = false,
bool macos = false,
}) {
flutterRoot = globals.fs.path.normalize(flutterRoot);
......@@ -634,12 +645,14 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
'withDriverTest': renderDriverTest,
'pluginClass': pluginClass,
'pluginDartClass': pluginDartClass,
'pluginCppHeaderGuard': projectName.toUpperCase(),
'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage,
'iosLanguage': iosLanguage,
'flutterRevision': globals.flutterVersion.frameworkRevision,
'flutterChannel': globals.flutterVersion.channel,
'web': web,
'linux': linux,
'macos': macos,
'year': DateTime.now().year,
};
......
......@@ -4,6 +4,7 @@
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
......@@ -21,6 +22,20 @@ Future<void> buildLinux(LinuxProject linuxProject, BuildInfo buildInfo, {String
'to learn about adding Linux support to a project.');
}
// Check for incompatibility between the Flutter tool version and the project
// template version, since the tempalte isn't stable yet.
final int templateCompareResult = _compareTemplateVersions(linuxProject);
if (templateCompareResult < 0) {
throwToolExit('The Linux runner was created with an earlier version of the '
'template, which is not yet stable.\n\n'
'Delete the linux/ directory and re-run \'flutter create .\', '
're-applying any previous changes.');
} else if (templateCompareResult > 0) {
throwToolExit('The Linux runner was created with a newer version of the '
'template, which is not yet stable.\n\n'
'Upgrade Flutter and try again.');
}
final StringBuffer buffer = StringBuffer('''
# Generated code do not commit.
export FLUTTER_ROOT=${Cache.flutterRoot}
......@@ -75,3 +90,25 @@ export PROJECT_DIR=${linuxProject.project.directory.path}
}
flutterUsage.sendTiming('build', 'make-linux', Duration(milliseconds: sw.elapsedMilliseconds));
}
// Checks the template version of [project] against the current template
// version. Returns < 0 if the project is older than the current template, > 0
// if it's newer, and 0 if they match.
int _compareTemplateVersions(LinuxProject project) {
const String projectVersionBasename = '.template_version';
final int expectedVersion = int.parse(globals.fs.file(globals.fs.path.join(
globals.fs.path.absolute(Cache.flutterRoot),
'packages',
'flutter_tools',
'templates',
'app',
'linux.tmpl',
'flutter',
projectVersionBasename,
)).readAsStringSync());
final File projectVersionFile = project.managedDirectory.childFile(projectVersionBasename);
final int version = projectVersionFile.existsSync()
? int.tryParse(projectVersionFile.readAsStringSync())
: 0;
return version.compareTo(expectedVersion);
}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/file_system.dart';
import '../project.dart';
// The setting that controls the executable name in the linux makefile.
......@@ -11,9 +12,20 @@ const String _kBinaryNameVariable = 'BINARY_NAME=';
///
/// Returns `null` if it cannot be found.
String makefileExecutableName(LinuxProject project) {
for (final String line in project.makeFile.readAsLinesSync()) {
if (line.startsWith(_kBinaryNameVariable)) {
return line.split(_kBinaryNameVariable).last.trim();
// Support the binary name being set either in the Makefile, or in the
// separate configution include file used by the template.
final List<File> makeFiles = <File>[
project.makeFile.parent.childFile('app_configuration.mk'),
project.makeFile,
];
for (final File file in makeFiles) {
if (!file.existsSync()) {
continue;
}
for (final String line in file.readAsLinesSync()) {
if (line.startsWith(_kBinaryNameVariable)) {
return line.split(_kBinaryNameVariable).last.trim();
}
}
}
return null;
......
......@@ -99,6 +99,11 @@ class Template {
if (relativeDestinationPath.contains('web') && !web) {
return null;
}
// Only build a Linux project if explicitly asked.
final bool linux = context['linux'] as bool;
if (relativeDestinationPath.startsWith('linux.tmpl') && !linux) {
return null;
}
// Only build a macOS project if explicitly asked.
final bool macOS = context['macos'] as bool;
if (relativeDestinationPath.startsWith('macos.tmpl') && !macOS) {
......
include app_configuration.mk
# Default build type.
BUILD=debug
FLUTTER_MANAGED_DIR=flutter
FLUTTER_EPHEMERAL_DIR=$(FLUTTER_MANAGED_DIR)/ephemeral
# Configuration provided via flutter tool.
FLUTTER_CONFIG_FILE=$(FLUTTER_EPHEMERAL_DIR)/generated_config.mk
include $(FLUTTER_CONFIG_FILE)
# Dependency locations
FLUTTER_APP_DIR=$(CURDIR)/..
FLUTTER_APP_BUILD_DIR=$(FLUTTER_APP_DIR)/build
OUT_DIR=$(FLUTTER_APP_BUILD_DIR)/linux
OBJ_DIR=$(OUT_DIR)/obj/$(BUILD)
# Libraries
FLUTTER_LIB_NAME=flutter_linux_glfw
FLUTTER_LIB=$(FLUTTER_EPHEMERAL_DIR)/lib$(FLUTTER_LIB_NAME).so
# Tools
FLUTTER_BIN=$(FLUTTER_ROOT)/bin/flutter
LINUX_BUILD=$(FLUTTER_ROOT)/packages/flutter_tools/bin/tool_backend.sh
# Resources
ICU_DATA_NAME=icudtl.dat
ICU_DATA_SOURCE=$(FLUTTER_EPHEMERAL_DIR)/$(ICU_DATA_NAME)
FLUTTER_ASSETS_NAME=flutter_assets
FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_BUILD_DIR)/$(FLUTTER_ASSETS_NAME)
# Bundle structure
BUNDLE_OUT_DIR=$(OUT_DIR)/$(BUILD)
BUNDLE_DATA_DIR=$(BUNDLE_OUT_DIR)/data
BUNDLE_LIB_DIR=$(BUNDLE_OUT_DIR)/lib
BIN_OUT=$(BUNDLE_OUT_DIR)/$(BINARY_NAME)
ICU_DATA_OUT=$(BUNDLE_DATA_DIR)/$(ICU_DATA_NAME)
FLUTTER_LIB_OUT=$(BUNDLE_LIB_DIR)/$(notdir $(FLUTTER_LIB))
ALL_LIBS_OUT=$(FLUTTER_LIB_OUT) \
$(foreach lib,$(EXTRA_BUNDLED_LIBRARIES),$(BUNDLE_LIB_DIR)/$(notdir $(lib)))
# Add relevant code from the wrapper library, which is intended to be statically
# built into the client.
# Use abspath for the wrapper root, which can contain relative paths; the
# intermediate build files will be based on the source path, which will cause
# issues if they start with one or more '../'s.
WRAPPER_ROOT=$(abspath $(FLUTTER_EPHEMERAL_DIR)/cpp_client_wrapper_glfw)
WRAPPER_SOURCES= \
$(WRAPPER_ROOT)/flutter_window_controller.cc \
$(WRAPPER_ROOT)/plugin_registrar.cc \
$(WRAPPER_ROOT)/engine_method_result.cc
# Use abspath for extra sources, which may also contain relative paths (see
# note above about WRAPPER_ROOT).
SOURCES=main.cc window_configuration.cc \
flutter/generated_plugin_registrant.cc \
$(WRAPPER_SOURCES) $(abspath $(EXTRA_SOURCES))
# Headers
WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
INCLUDE_DIRS=$(FLUTTER_EPHEMERAL_DIR) $(WRAPPER_INCLUDE_DIR)
# Build settings
ifneq ($(strip $(SYSTEM_LIBRARIES)),)
EXTRA_CPPFLAGS+=$(patsubst -I%,-isystem%,$(shell pkg-config --cflags $(SYSTEM_LIBRARIES)))
EXTRA_LDFLAGS+=$(shell pkg-config --libs $(SYSTEM_LIBRARIES))
endif
CXX=clang++
CPPFLAGS.release=-DNDEBUG
CPPFLAGS.profile=$(CPPFLAGS.release)
CXXFLAGS.release=-O2
CXXFLAGS.profile=$(CXXFLAGS.release)
CXXFLAGS=-std=c++14 -Wall -Werror $(CXXFLAGS.$(BUILD)) $(EXTRA_CXXFLAGS)
CPPFLAGS=$(patsubst %,-I%,$(INCLUDE_DIRS)) \
$(CPPFLAGS.$(BUILD)) $(EXTRA_CPPFLAGS)
LDFLAGS=-L$(BUNDLE_LIB_DIR) \
-l$(FLUTTER_LIB_NAME) \
$(EXTRA_LDFLAGS) \
-Wl,-rpath=\$$ORIGIN/lib
# Intermediate files.
OBJ_FILES=$(SOURCES:%.cc=$(OBJ_DIR)/%.o)
DEPENDENCY_FILES=$(OBJ_FILES:%.o=%.d)
# Targets
.PHONY: all
all: $(BIN_OUT) bundle
# Add the plugin targets, and their associated settings.
include $(FLUTTER_MANAGED_DIR)/generated_plugins.mk
EXTRA_BUNDLED_LIBRARIES+=$(PLUGIN_LIBRARIES)
EXTRA_LDFLAGS+=$(PLUGIN_LDFLAGS)
EXTRA_CPPFLAGS+=$(PLUGIN_CPPFLAGS)
# This is a phony target because the flutter tool cannot describe
# its inputs and outputs yet.
.PHONY: sync
sync: $(FLUTTER_CONFIG_FILE)
$(LINUX_BUILD) linux-x64 $(BUILD)
.PHONY: bundle
bundle: $(ICU_DATA_OUT) $(ALL_LIBS_OUT) bundleflutterassets
$(BIN_OUT): $(OBJ_FILES) $(ALL_LIBS_OUT)
mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OBJ_FILES) $(LDFLAGS) -o $@
$(WRAPPER_SOURCES) $(FLUTTER_LIB) $(ICU_DATA_SOURCE) $(FLUTTER_ASSETS_SOURCE) \
$(PLUGIN_TARGETS): | sync
# Plugin library bundling pattern.
$(BUNDLE_LIB_DIR)/%: $(OUT_DIR)/%
mkdir -p $(BUNDLE_LIB_DIR)
cp $< $@
$(FLUTTER_LIB_OUT): $(FLUTTER_LIB)
mkdir -p $(@D)
cp $< $@
$(ICU_DATA_OUT): $(ICU_DATA_SOURCE)
mkdir -p $(@D)
cp $< $@
-include $(DEPENDENCY_FILES)
$(OBJ_DIR)/%.o : %.cc | sync
mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -c $< -o $@
# Fully re-copy the assets directory on each build to avoid having to keep a
# comprehensive list of all asset files here, which would be fragile to changes
# in other files (e.g., adding a new font to pubspec.yaml).
.PHONY: bundleflutterassets
bundleflutterassets: $(FLUTTER_ASSETS_SOURCE)
mkdir -p $(BUNDLE_DATA_DIR)
rsync -rpu --delete $(FLUTTER_ASSETS_SOURCE) $(BUNDLE_DATA_DIR)
.PHONY: clean
clean:
rm -rf $(OUT_DIR); \
cd $(FLUTTER_APP_DIR); \
$(FLUTTER_BIN) clean
# This file contains variables that applications are likely to need to
# change, to isolate them from the main Makefile where the build rules are still
# in flux. This should simplify re-creating the runner while preserving local
# changes.
# Executable name.
BINARY_NAME={{projectName}}
# Any extra source files to build.
EXTRA_SOURCES=
# Paths of any additional libraries to be bundled in the output directory.
EXTRA_BUNDLED_LIBRARIES=
# Extra flags (e.g., for library dependencies).
SYSTEM_LIBRARIES=
EXTRA_CXXFLAGS=
EXTRA_CPPFLAGS=
EXTRA_LDFLAGS=
\ No newline at end of file
#include <flutter/flutter_window_controller.h>
#include <linux/limits.h>
#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>
#include "flutter/generated_plugin_registrant.h"
#include "window_configuration.h"
namespace {
// Returns the path of the directory containing this executable, or an empty
// string if the directory cannot be found.
std::string GetExecutableDirectory() {
char buffer[PATH_MAX + 1];
ssize_t length = readlink("/proc/self/exe", buffer, sizeof(buffer));
if (length > PATH_MAX) {
std::cerr << "Couldn't locate executable" << std::endl;
return "";
}
std::string executable_path(buffer, length);
size_t last_separator_position = executable_path.find_last_of('/');
if (last_separator_position == std::string::npos) {
std::cerr << "Unabled to find parent directory of " << executable_path
<< std::endl;
return "";
}
return executable_path.substr(0, last_separator_position);
}
} // namespace
int main(int argc, char **argv) {
// Resources are located relative to the executable.
std::string base_directory = GetExecutableDirectory();
if (base_directory.empty()) {
base_directory = ".";
}
std::string data_directory = base_directory + "/data";
std::string assets_path = data_directory + "/flutter_assets";
std::string icu_data_path = data_directory + "/icudtl.dat";
// 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)) {
return EXIT_FAILURE;
}
RegisterPlugins(&flutter_controller);
// Run until the window is closed.
while (flutter_controller.RunEventLoopWithTimeout(
std::chrono::milliseconds::max())) {
}
return EXIT_SUCCESS;
}
#include "window_configuration.h"
const char *kFlutterWindowTitle = "{{projectName}}";
const unsigned int kFlutterWindowWidth = 800;
const unsigned int kFlutterWindowHeight = 600;
#ifndef WINDOW_CONFIGURATION_
#define WINDOW_CONFIGURATION_
// This is a temporary approach to isolate common customizations from main.cpp,
// where the APIs are still in flux. This should simplify re-creating the
// runner while preserving local changes.
//
// Longer term there should be simpler configuration options for common
// customizations like this, without requiring native code changes.
extern const char *kFlutterWindowTitle;
extern const unsigned int kFlutterWindowWidth;
extern const unsigned int kFlutterWindowHeight;
#endif // WINDOW_CONFIGURATION_
include plugin_configuration.mk
# Default build type.
BUILD=debug
# Plugins must be provided a populated flutter/ephemeral/ dir. Normally this
# would be the ephemeral directory of the application using the plugin.
ifeq ($(strip $(FLUTTER_EPHEMERAL_DIR)),)
$(error FLUTTER_EPHEMERAL_DIR must be provided)
endif
# Dependency locations
# Default to building in the plugin directory.
OUT_DIR=$(CURDIR)/../build/linux
# Sharing an OUT_DIR will be common, so use a subdirectory for intermediates.
PLUGIN_OUT_DIR=$(OUT_DIR)/$(PLUGIN_NAME)
OBJ_DIR=$(PLUGIN_OUT_DIR)/obj/$(BUILD)
# Flutter library
FLUTTER_LIB_NAME=flutter_linux_glfw
FLUTTER_LIB=$(FLUTTER_EPHEMERAL_DIR)/lib$(FLUTTER_LIB_NAME).so
# Add relevant code from the wrapper library, which is intended to be statically
# built into the plugin.
# Use abspath for the wrapper root, which can contain relative paths; the
# intermediate build files will be based on the source path, which will cause
# issues if they start with one or more '../'s.
WRAPPER_ROOT=$(abspath $(FLUTTER_EPHEMERAL_DIR)/cpp_client_wrapper_glfw)
# TODO: Once JSON codec files are merged, make a PLUGIN_CODEC variable in the
# top section. For now, using JSON codec would require changes here.
WRAPPER_SOURCES= \
$(WRAPPER_ROOT)/engine_method_result.cc \
$(WRAPPER_ROOT)/plugin_registrar.cc \
$(WRAPPER_ROOT)/standard_codec.cc
# Use abspath for extra sources, which may also contain relative paths (see
# note above about WRAPPER_ROOT).
SOURCES=$(PLUGIN_NAME)_plugin.cc $(WRAPPER_SOURCES) $(abspath $(EXTRA_SOURCES))
WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
INCLUDE_DIRS=$(FLUTTER_EPHEMERAL_DIR) $(WRAPPER_INCLUDE_DIR)
# Build settings
CXX=clang++
CPPFLAGS.release=-DNDEBUG
CPPFLAGS.profile=$(CPPFLAGS.release)
CXXFLAGS.release=-O2
CXXFLAGS.profile=$(CXXFLAGS.release)
CXXFLAGS=-std=c++14 -Wall -Werror -fPIC -fvisibility=hidden \
$(CXXFLAGS.$(BUILD)) $(EXTRA_CXXFLAGS)
CPPFLAGS=-DFLUTTER_PLUGIN_IMPL $(patsubst %,-I%,$(INCLUDE_DIRS)) \
$(CPPFLAGS.$(BUILD)) $(EXTRA_CPPFLAGS)
LDFLAGS=-shared -L$(FLUTTER_EPHEMERAL_DIR) -l$(FLUTTER_LIB_NAME) $(EXTRA_LDFLAGS)
# Final output files that will be used by applications.
LIBRARY_OUT=$(OUT_DIR)/lib$(PLUGIN_NAME)_plugin.so
# Intermediate files.
OBJ_FILES=$(SOURCES:%.cc=$(OBJ_DIR)/%.o)
DEPENDENCY_FILES=$(OBJ_FILES:%.o=%.d)
# Targets
.PHONY: all
all: $(PLUGIN_NAME)
.PHONY: $(PLUGIN_NAME)
$(PLUGIN_NAME) : $(LIBRARY_OUT)
$(LIBRARY_OUT): $(OBJ_FILES)
mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ $(LDFLAGS) -o $@
-include $(DEPENDENCY_FILES)
$(OBJ_DIR)/%.o : %.cc
mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -c $< -o $@
.PHONY: clean
clean:
rm -f $(LIBRARY_OUT)
rm -rf $(PLUGINOUT_DIR)
# The name of the plugin.
# The primary source file is assumed to be $(PLUGIN_NAME)_plugin.cc, with
# $(PLUGIN_NAME)_plugin.h as the public header meant for inclusion by the
# application.
PLUGIN_NAME={{projectName}}
# Any files other than the plugin class files that need to be compiled.
EXTRA_SOURCES=
# Extra flags (e.g., for library dependencies).
EXTRA_CXXFLAGS=
EXTRA_CPPFLAGS=
EXTRA_LDFLAGS=
\ No newline at end of file
#include "sample_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_glfw.h>
#include <flutter/standard_method_codec.h>
#include <sys/utsname.h>
#include <map>
#include <memory>
#include <sstream>
namespace {
class {{pluginClass}} : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarGlfw *registrar);
{{pluginClass}}();
virtual ~{{pluginClass}}();
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);
};
// 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}}>();
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
{{pluginClass}}::{{pluginClass}}() {}
{{pluginClass}}::~{{pluginClass}}() {}
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();
}
}
} // namespace
void {{pluginClass}}RegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
// The plugin registrar wrappers owns the plugins, registered callbacks, etc.,
// so must remain valid for the life of the application.
static auto *plugin_registrars =
new std::map<FlutterDesktopPluginRegistrarRef,
std::unique_ptr<flutter::PluginRegistrarGlfw>>;
auto insert_result = plugin_registrars->emplace(
registrar, std::make_unique<flutter::PluginRegistrarGlfw>(registrar));
{{pluginClass}}::RegisterWithRegistrar(insert_result.first->second.get());
}
#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_
......@@ -32,6 +32,10 @@ flutter:
pluginClass: {{pluginClass}}
ios:
pluginClass: {{pluginClass}}
{{#linux}}
linux:
pluginClass: {{pluginClass}}
{{/linux}}
{{#macos}}
macos:
pluginClass: {{pluginClass}}
......
......@@ -20,16 +20,18 @@ import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
const String _kTestFlutterRoot = '/flutter';
final Platform linuxPlatform = FakePlatform(
operatingSystem: 'linux',
environment: <String, String>{
'FLUTTER_ROOT': '/',
'FLUTTER_ROOT': _kTestFlutterRoot
}
);
final Platform notLinuxPlatform = FakePlatform(
operatingSystem: 'macos',
environment: <String, String>{
'FLUTTER_ROOT': '/',
'FLUTTER_ROOT': _kTestFlutterRoot,
}
);
......@@ -44,6 +46,7 @@ void main() {
setUp(() {
fileSystem = MemoryFileSystem.test();
Cache.flutterRoot = _kTestFlutterRoot;
});
// Creates the mock files necessary to look like a Flutter project.
......@@ -54,9 +57,29 @@ void main() {
}
// Creates the mock files necessary to run a build.
void setUpMockProjectFilesForBuild() {
fileSystem.file(fileSystem.path.join('linux', 'Makefile')).createSync(recursive: true);
void setUpMockProjectFilesForBuild({int templateVersion}) {
setUpMockCoreProjectFiles();
fileSystem.file(fileSystem.path.join('linux', 'Makefile')).createSync(recursive: true);
final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version');
const int expectedTemplateVersion = 10; // Arbitrary value for tests.
final File sourceTemplateVersionfile = fileSystem.file(fileSystem.path.join(
fileSystem.path.absolute(Cache.flutterRoot),
'packages',
'flutter_tools',
'templates',
'app',
'linux.tmpl',
versionFileSubpath,
));
sourceTemplateVersionfile.createSync(recursive: true);
sourceTemplateVersionfile.writeAsStringSync(expectedTemplateVersion.toString());
final File projectTemplateVersionFile = fileSystem.file(
fileSystem.path.join('linux', versionFileSubpath));
templateVersion ??= expectedTemplateVersion;
projectTemplateVersionFile.createSync(recursive: true);
projectTemplateVersionFile.writeAsStringSync(templateVersion.toString());
}
testUsingContext('Linux build fails when there is no linux project', () async {
......@@ -87,6 +110,34 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('Linux build fails with instructions when template is too old', () async {
final BuildCommand command = BuildCommand();
setUpMockProjectFilesForBuild(templateVersion: 1);
expect(createTestCommandRunner(command).run(
const <String>['build', 'linux']
), throwsToolExit(message: 'flutter create .'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => linuxPlatform,
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('Linux build fails with instructions when template is too new', () async {
final BuildCommand command = BuildCommand();
setUpMockProjectFilesForBuild(templateVersion: 999);
expect(createTestCommandRunner(command).run(
const <String>['build', 'linux']
), throwsToolExit(message: 'Upgrade Flutter'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => linuxPlatform,
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('Linux build invokes make and writes temporary files', () async {
final BuildCommand command = BuildCommand();
processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -225,6 +276,32 @@ BINARY_NAME=fizz_bar
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('linux can extract binary name from app config', () async {
fileSystem.file('linux/Makefile')
..createSync(recursive: true)
..writeAsStringSync(r'''
# Comment
SOMETHING_ELSE=FOO
include app_configuration.mk
''');
fileSystem.file('linux/app_configuration.mk')
..createSync(recursive: true)
..writeAsStringSync(r'''
# Comment
SOMETHING_ELSE=FOO
BINARY_NAME=fizz_bar
''');
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(makefileExecutableName(flutterProject.linux), 'fizz_bar');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('Refuses to build for Linux when feature is disabled', () {
final CommandRunner<void> runner = createTestCommandRunner(BuildCommand());
......
......@@ -585,7 +585,7 @@ void main() {
expect(actualContents.contains('useAndroidX'), false);
});
testUsingContext('app supports macOS if requested', () async {
testUsingContext('app supports Linux if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
......@@ -595,9 +595,54 @@ void main() {
await runner.run(<String>['create', '--no-pub', projectDir.path]);
expect(projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace').existsSync(), true);
expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), true);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('app does not include Linux by default', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), false);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
});
testUsingContext('plugin supports Linux if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), true);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('plugin does not include Linux by default', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), false);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
});
testUsingContext('app does not include macOS by default', () async {
......
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