From d10fcb612771d3791030bd6c7c9dbd0afef74a5f Mon Sep 17 00:00:00 2001 From: Hidenori Matsubayashi Date: Fri, 16 Jul 2021 13:03:30 +0900 Subject: [PATCH] first commit --- .github/workflows/dart.yml | 23 + .gitignore | 114 +++++ AUTHORS | 7 + CONTRIBUTING.md | 2 + LICENSE | 27 + README.md | 21 + analysis_options.yaml | 20 + bin/flutter-elinux | 65 +++ bin/flutter_elinux.dart | 12 + bin/internal/engine.version | 1 + bin/internal/flutter.version | 1 + bin/internal/shared.sh | 101 ++++ lib/commands/analyze.dart | 24 + lib/commands/attach.dart | 15 + lib/commands/build.dart | 99 ++++ lib/commands/clean.dart | 54 ++ lib/commands/create.dart | 163 ++++++ lib/commands/drive.dart | 21 + lib/commands/packages.dart | 106 ++++ lib/commands/precache.dart | 86 ++++ lib/commands/run.dart | 31 ++ lib/commands/test.dart | 15 + lib/elinux_build_target.dart | 436 +++++++++++++++++ lib/elinux_builder.dart | 150 ++++++ lib/elinux_cache.dart | 133 +++++ lib/elinux_cmake_project.dart | 54 ++ lib/elinux_device.dart | 356 ++++++++++++++ lib/elinux_device_discovery.dart | 132 +++++ lib/elinux_doctor.dart | 126 +++++ lib/elinux_package.dart | 103 ++++ lib/elinux_plugins.dart | 462 ++++++++++++++++++ lib/executable.dart | 144 ++++++ pubspec.yaml | 26 + templates/app/CMakeLists.txt.tmpl | 104 ++++ templates/app/flutter/CMakeLists.txt | 108 ++++ templates/app/runner/CMakeLists.txt | 23 + templates/app/runner/command_options.h | 367 ++++++++++++++ .../app/runner/flutter_embedder_options.h | 103 ++++ templates/app/runner/flutter_window.cc | 79 +++ templates/app/runner/flutter_window.h | 34 ++ templates/app/runner/main.cc | 46 ++ templates/template_manifest.json | 293 +++++++++++ 42 files changed, 4287 insertions(+) create mode 100644 .github/workflows/dart.yml create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 bin/flutter-elinux create mode 100644 bin/flutter_elinux.dart create mode 100644 bin/internal/engine.version create mode 100644 bin/internal/flutter.version create mode 100644 bin/internal/shared.sh create mode 100644 lib/commands/analyze.dart create mode 100644 lib/commands/attach.dart create mode 100644 lib/commands/build.dart create mode 100644 lib/commands/clean.dart create mode 100644 lib/commands/create.dart create mode 100644 lib/commands/drive.dart create mode 100644 lib/commands/packages.dart create mode 100644 lib/commands/precache.dart create mode 100644 lib/commands/run.dart create mode 100644 lib/commands/test.dart create mode 100644 lib/elinux_build_target.dart create mode 100644 lib/elinux_builder.dart create mode 100644 lib/elinux_cache.dart create mode 100644 lib/elinux_cmake_project.dart create mode 100644 lib/elinux_device.dart create mode 100644 lib/elinux_device_discovery.dart create mode 100644 lib/elinux_doctor.dart create mode 100644 lib/elinux_package.dart create mode 100644 lib/elinux_plugins.dart create mode 100644 lib/executable.dart create mode 100644 pubspec.yaml create mode 100644 templates/app/CMakeLists.txt.tmpl create mode 100644 templates/app/flutter/CMakeLists.txt create mode 100644 templates/app/runner/CMakeLists.txt create mode 100644 templates/app/runner/command_options.h create mode 100644 templates/app/runner/flutter_embedder_options.h create mode 100644 templates/app/runner/flutter_window.cc create mode 100644 templates/app/runner/flutter_window.h create mode 100644 templates/app/runner/main.cc create mode 100644 templates/template_manifest.json diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 0000000..da65a01 --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,23 @@ +name: Dart + +on: + push: + pull_request: + +jobs: + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Fetch Flutter SDK + run: | + git clone --depth=1 https://github.com/flutter/flutter.git + cd flutter + git fetch --depth=1 https://github.com/flutter/flutter.git `cat ../bin/internal/flutter.version` + git checkout FETCH_HEAD + - name: Install pub dependencies + run: flutter/bin/flutter pub get + - name: Verify formatting + run: flutter/bin/dart format --output=none --set-exit-if-changed lib + - name: Analyze project source + run: flutter/bin/dart analyze --fatal-infos lib diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7d74c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/ + +# Flutter repo-specific +/bin/cache/ +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/Flutter-Debug.xcconfig +**/macos/Flutter/Flutter-Release.xcconfig +**/macos/Flutter/Flutter-Profile.xcconfig + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..33562e8 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +# Below is a list of people and organizations that have contributed +# to sony/flutter-elinux from after forking flutter-tizen. Names should +# be added to the list like so: +# +# Name/Organization + +Sony Group Corporation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..99247dd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,2 @@ +# Contributing to flutter-elinux +Welcome to this project. We welcome all your contribution and feedback. If you've never submitted code before, you must add your (or your organization's) name and contact info to the [AUTHORS](./AUTHORS) file. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a41b445 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2021 Sony Group Corporation. All rights reserved. +Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. +Copyright (c) 2014 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the names of the copyright holders nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f068247 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Flutter for Embedded Linux (eLinux) +This software is a **non-official** extension to the [Flutter SDK](https://github.com/flutter/flutter) to build Flutter apps for Embedded Linux devices. + +## Documentation +See https://github.com/sony/flutter-elinux/wiki + +## Contributing +See [CONTRIBUTING.md](CONTRIBUTING.md). + +## Related projects/repos +### Flutter plugins for eLinux +- [flutter-elinux-plugins](https://github.com/sony/flutter-elinux-plugins) + +### Flutter embedder (engine) for eLinux +- [flutter-embedded-linux](https://github.com/sony/flutter-embedded-linux) + +### Yocto recipes for Flutter embedder for eLinux +- [meta-flutter](https://github.com/sony/meta-flutter) + +## Base software +This software was created based on the [flutter-tizen](https://github.com/flutter-tizen/flutter-tizen) (branched from [this version](https://github.com/flutter-tizen/flutter-tizen/commit/ed128233c0bce33c77dd0df69afa59f0888d2d00)). Special thanks to the flutter-tizen team. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..5959463 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,20 @@ +include: flutter/packages/flutter_tools/analysis_options.yaml + +analyzer: + exclude: + - "flutter/bin/cache/**" + - "flutter/dev/**" + - "flutter/examples/**" + - "flutter/packages/flutter/**" + - "flutter/packages/flutter_driver/**" + - "flutter/packages/flutter_goldens/**" + - "flutter/packages/flutter_goldens_client/**" + - "flutter/packages/flutter_localizations/**" + - "flutter/packages/flutter_test/**" + - "flutter/packages/flutter_web_plugins/**" + - "flutter/packages/fuchsia_remote_debug_protocol/**" + - "flutter/packages/integration_test/**" + +linter: + rules: + implementation_imports: false diff --git a/bin/flutter-elinux b/bin/flutter-elinux new file mode 100644 index 0000000..20bf277 --- /dev/null +++ b/bin/flutter-elinux @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Copyright 2021 Sony Group Corporation. All rights reserved. +# Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +# Copyright 2014 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +# Needed because if it is set, cd may print the path it changed to. +unset CDPATH + +# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one +# link at a time, and then cds into the link destination and find out where it +# ends up. +# +# The returned filesystem path must be a format usable by Dart's URI parser, +# since the Dart command line tool treats its argument as a file URI, not a +# filename. For instance, multiple consecutive slashes should be reduced to a +# single slash, since double-slashes indicate a URI "authority", and these are +# supposed to be filenames. There is an edge case where this will return +# multiple slashes: when the input resolves to the root directory. However, if +# that were the case, we wouldn't be running this shell, so we don't do anything +# about it. +# +# The function is enclosed in a subshell to avoid changing the working directory +# of the caller. +function follow_links() ( + cd -P "$(dirname -- "$1")" + file="$PWD/$(basename -- "$1")" + while [[ -h "$file" ]]; do + cd -P "$(dirname -- "$file")" + file="$(readlink -- "$file")" + cd -P "$(dirname -- "$file")" + file="$PWD/$(basename -- "$file")" + done + echo "$file" +) + +PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")" +BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" +OS="$(uname -s)" + +# Allow only 64-bit host platform. +if [ "$(getconf LONG_BIT)" != "64" ]; then + echo "$(basename $PROG_NAME) only supports 64 bit platforms." + exit 1 +fi + +# If we're on Windows, invoke the batch script instead to get proper locking. +if [[ $OS =~ MINGW.* || $OS =~ CYGWIN.* ]]; then + echo "Window platforms are not supported." + exit 1 +fi + +source "$BIN_DIR/internal/shared.sh" + +# Clone or update the flutter repo. +update_flutter + +# Upgrade flutter-elinux if needed. +update_flutter_elinux + +# Run the snapshot. +exec_snapshot "$@" diff --git a/bin/flutter_elinux.dart b/bin/flutter_elinux.dart new file mode 100644 index 0000000..72ba4ff --- /dev/null +++ b/bin/flutter_elinux.dart @@ -0,0 +1,12 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_elinux/executable.dart' as executable; + +void main(List args) { + executable.main(args); +} diff --git a/bin/internal/engine.version b/bin/internal/engine.version new file mode 100644 index 0000000..329298f --- /dev/null +++ b/bin/internal/engine.version @@ -0,0 +1 @@ +9d517f475ba1282b619477bde8e708d6a34287cf diff --git a/bin/internal/flutter.version b/bin/internal/flutter.version new file mode 100644 index 0000000..a97db27 --- /dev/null +++ b/bin/internal/flutter.version @@ -0,0 +1 @@ +615957513eb43b128a16048ab5aa2daaba1656cd diff --git a/bin/internal/shared.sh b/bin/internal/shared.sh new file mode 100644 index 0000000..cd97b7e --- /dev/null +++ b/bin/internal/shared.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Copyright 2021 Sony Group Corporation. All rights reserved. +# Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# ---------------------------------- NOTE ---------------------------------- # +# +# Please keep the logic in this file consistent with the logic in the +# `shared.bat` script in the same directory to ensure that Flutter continue +# to work across all platforms! +# +# -------------------------------------------------------------------------- # + +set -e + +# Needed because if it is set, cd may print the path it changed to. +unset CDPATH + +FLUTTER_REPO="https://github.com/flutter/flutter.git" + +if [[ -z "$BIN_DIR" ]]; then + echo "BIN_DIR is not set." + exit 1 +fi +ROOT_DIR="$(cd "${BIN_DIR}/.." ; pwd -P)" +FLUTTER_DIR="$ROOT_DIR/flutter" +SNAPSHOT_PATH="$ROOT_DIR/bin/cache/flutter-elinux.snapshot" + +FLUTTER_EXE="$FLUTTER_DIR/bin/flutter" +DART_EXE="$FLUTTER_DIR/bin/cache/dart-sdk/bin/dart" + +function update_flutter() { + if [[ -e "$FLUTTER_DIR" && ! -d "$FLUTTER_DIR/.git" ]]; then + echo "$FLUTTER_DIR is not a git directory. Remove it and try again." + exit 1 + fi + + # Clone flutter repo if not installed. + if [[ ! -d "$FLUTTER_DIR" ]]; then + git clone --depth=1 "$FLUTTER_REPO" "$FLUTTER_DIR" + fi + + # GIT_DIR and GIT_WORK_TREE are used in the git command. + export GIT_DIR="$FLUTTER_DIR/.git" + export GIT_WORK_TREE="$FLUTTER_DIR" + + # Update flutter repo if needed. + local version="$(cat "$ROOT_DIR/bin/internal/flutter.version")" + if [[ "$version" != "$(git rev-parse HEAD)" ]]; then + git reset --hard + git clean -xdf + git fetch --depth=1 "$FLUTTER_REPO" "$version" + git checkout FETCH_HEAD + + # Invalidate the cache. + rm -fr "$ROOT_DIR/bin/cache" + fi + + if [[ "$version" != "$(git rev-parse HEAD)" ]]; then + echo "Something went wrong when upgrading the Flutter SDK." \ + "Remove directory $FLUTTER_DIR and try again." + exit 1 + fi + + unset GIT_DIR + unset GIT_WORK_TREE + + # Invalidate the flutter cache. + local stamp_path="$FLUTTER_DIR/bin/cache/flutter_tools.stamp" + if [[ ! -f "$stamp_path" || "$version" != "$(cat "$stamp_path")" ]]; then + "$FLUTTER_EXE" --version + fi +} + +function update_flutter_elinux() { + mkdir -p "$ROOT_DIR/bin/cache" + + local revision="$(git --git-dir="$ROOT_DIR/.git" rev-parse HEAD)" + local stamp_path="$ROOT_DIR/bin/cache/flutter-elinux.stamp" + + if [[ ! -f "$SNAPSHOT_PATH" || ! -s "$stamp_path" || "$revision" != "$(cat "$stamp_path")" + || "$ROOT_DIR/pubspec.yaml" -nt "$ROOT_DIR/pubspec.lock" ]]; then + echo "Running pub upgrade..." + (cd "$ROOT_DIR" && "$FLUTTER_EXE" pub upgrade) || { + >&2 echo "Error: Unable to 'pub upgrade' flutter-elinux." + exit 1 + } + + echo "Compiling flutter-elinux..." + "$DART_EXE" --disable-dart-dev --no-enable-mirrors \ + --snapshot="$SNAPSHOT_PATH" --packages="$ROOT_DIR/.packages" \ + "$ROOT_DIR/bin/flutter_elinux.dart" + + echo "$revision" > "$stamp_path" + fi +} + +function exec_snapshot() { + "$DART_EXE" --disable-dart-dev --packages="$ROOT_DIR/.packages" "$SNAPSHOT_PATH" "$@" +} diff --git a/lib/commands/analyze.dart b/lib/commands/analyze.dart new file mode 100644 index 0000000..645518a --- /dev/null +++ b/lib/commands/analyze.dart @@ -0,0 +1,24 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/commands/analyze.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; + +import '../elinux_plugins.dart'; + +class ELinuxAnalyzeCommand extends AnalyzeCommand with ELinuxExtension { + ELinuxAnalyzeCommand({bool verboseHelp = false}) + : super( + verboseHelp: verboseHelp, + fileSystem: globals.fs, + platform: globals.platform, + processManager: globals.processManager, + logger: globals.logger, + terminal: globals.terminal, + artifacts: globals.artifacts, + ); +} diff --git a/lib/commands/attach.dart b/lib/commands/attach.dart new file mode 100644 index 0000000..ea84955 --- /dev/null +++ b/lib/commands/attach.dart @@ -0,0 +1,15 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/commands/attach.dart'; + +import '../elinux_plugins.dart'; + +class ELinuxAttachCommand extends AttachCommand with ELinuxExtension { + ELinuxAttachCommand({bool verboseHelp = false}) + : super(verboseHelp: verboseHelp); +} diff --git a/lib/commands/build.dart b/lib/commands/build.dart new file mode 100644 index 0000000..2d1a5b7 --- /dev/null +++ b/lib/commands/build.dart @@ -0,0 +1,99 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/android/build_validation.dart' as android; +import 'package:flutter_tools/src/base/analyze_size.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/commands/build.dart'; +import 'package:flutter_tools/src/commands/build_apk.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; + +import '../elinux_builder.dart'; +import '../elinux_cache.dart'; +import '../elinux_plugins.dart'; + +class ELinuxBuildCommand extends BuildCommand { + ELinuxBuildCommand({bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + addSubcommand(BuildPackageCommand(verboseHelp: verboseHelp)); + } +} + +class BuildPackageCommand extends BuildSubCommand with ELinuxExtension { + /// See: [BuildApkCommand] in `build_apk.dart` + BuildPackageCommand({bool verboseHelp = false}) { + addCommonDesktopBuildOptions(verboseHelp: verboseHelp); + usesBuildNameOption(); + argParser.addOption( + 'target-arch', + defaultsTo: _getCurrentHostPlatformArchName(), + allowed: ['x64', 'arm64'], + help: 'Target architecture for which the the app is compiled', + ); + argParser.addOption( + 'target-backend-type', + defaultsTo: 'wayland', + allowed: ['wayland', 'gbm', 'eglstream', 'x11'], + help: 'Target backend type that the app will run on devices.', + ); + } + + @override + final String name = 'elinux'; + + @override + Future> get requiredArtifacts async => + { + DevelopmentArtifact.androidGenSnapshot, + ELinuxDevelopmentArtifact.elinux, + }; + + @override + final String description = 'Build an eLinux package from your app.'; + + /// See: [android.validateBuild] in `build_validation.dart` + void validateBuild(ELinuxBuildInfo eLinuxBuildInfo) { + if (eLinuxBuildInfo.buildInfo.mode.isPrecompiled && + eLinuxBuildInfo.targetArch == 'x86') { + throwToolExit('x86 ABI does not support AOT compilation.'); + } + } + + /// See: [BuildApkCommand.runCommand] in `build_apk.dart` + @override + Future runCommand() async { + final BuildInfo buildInfo = await getBuildInfo(); + final ELinuxBuildInfo eLinuxBuildInfo = ELinuxBuildInfo( + buildInfo, + targetArch: stringArg('target-arch'), + targetBackendType: stringArg('target-backend-type'), + ); + validateBuild(eLinuxBuildInfo); + displayNullSafetyMode(buildInfo); + + await ELinuxBuilder.buildBundle( + project: FlutterProject.current(), + targetFile: targetFile, + eLinuxBuildInfo: eLinuxBuildInfo, + sizeAnalyzer: SizeAnalyzer( + fileSystem: globals.fs, + logger: globals.logger, + flutterUsage: globals.flutterUsage, + ), + ); + return FlutterCommandResult.success(); + } + + String _getCurrentHostPlatformArchName() { + final HostPlatform hostPlatform = getCurrentHostPlatform(); + return getNameForHostPlatformArch(hostPlatform); + } +} diff --git a/lib/commands/clean.dart b/lib/commands/clean.dart new file mode 100644 index 0000000..c98de71 --- /dev/null +++ b/lib/commands/clean.dart @@ -0,0 +1,54 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/commands/clean.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:path/path.dart'; + +import '../elinux_cmake_project.dart'; + +class ELinuxCleanCommand extends CleanCommand { + ELinuxCleanCommand({bool verbose = false}) : super(verbose: verbose); + + /// See: [CleanCommand.runCommand] in `clean.dart` + @override + Future runCommand() async { + final FlutterProject flutterProject = FlutterProject.current(); + _cleanELinuxProject(ELinuxProject.fromFlutter(flutterProject)); + + return super.runCommand(); + } + + void _cleanELinuxProject(ELinuxProject project) { + if (!project.existsSync()) { + return; + } + _deleteFile(project.ephemeralDirectory); + } + + /// Source: [CleanCommand.deleteFile] in `clean.dart` (simplified) + void _deleteFile(FileSystemEntity file) { + if (!file.existsSync()) { + return; + } + final String path = relative(file.path); + final Status status = globals.logger.startProgress( + 'Deleting $path...', + ); + try { + file.deleteSync(recursive: true); + } on FileSystemException catch (error) { + globals.printError('Failed to remove $path: $error'); + } finally { + status?.stop(); + } + } +} diff --git a/lib/commands/create.dart b/lib/commands/create.dart new file mode 100644 index 0000000..3293f4a --- /dev/null +++ b/lib/commands/create.dart @@ -0,0 +1,163 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/terminal.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/create.dart'; +import 'package:flutter_tools/src/flutter_project_metadata.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:flutter_tools/src/template.dart'; + +import '../elinux_plugins.dart'; + +class ELinuxCreateCommand extends CreateCommand { + ELinuxCreateCommand({bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + argParser.addOption( + 'target-backend-type', + defaultsTo: 'wayland', + allowed: ['wayland', 'gbm', 'eglstream', 'x11'], + help: 'Target backend type that the app will run on devices.', + ); + } + + @override + void printUsage() { + super.printUsage(); + // TODO(swift-kim): I couldn't find a proper way to override the --platforms + // option without copying the entire class. This message is a workaround. + print( + 'You don\'t have to specify "elinux" as a target platform with ' + '"--platforms" option. It is automatically added by default.', + ); + } + + /// See: + /// - [CreateCommand.runCommand] in `create.dart` + /// - [CreateCommand._getProjectType] in `create.dart` (generatePlugin) + Future runInternal() async { + final FlutterCommandResult result = await super.runCommand(); + if (result != FlutterCommandResult.success() || argResults.rest.isEmpty) { + return result; + } + + final bool generatePlugin = argResults['template'] != null + ? stringArg('template') == + flutterProjectTypeToString(FlutterProjectType.plugin) + : determineTemplateType() == FlutterProjectType.plugin; + if (generatePlugin) { + // Assume that pubspec.yaml uses the multi-platforms plugin format if the + // file already exists. + // TODO(swift-kim): Skip this message if elinux already exists in pubspec. + globals.printStatus( + 'The `pubspec.yaml` under the project directory must be updated to support ELinux.\n' + 'Add below lines to under the `platforms:` key.', + emphasis: true, + color: TerminalColor.yellow, + ); + final Map templateContext = createTemplateContext( + organization: '', + projectName: projectName, + flutterRoot: '', + ); + globals.printStatus( + '\nelinux:\n' + ' pluginClass: ${templateContext['pluginClass'] as String}\n' + ' fileName: ${projectName}_plugin.h', + emphasis: true, + color: TerminalColor.blue, + ); + globals.printStatus(''); + } + + if (boolArg('pub')) { + final FlutterProject project = FlutterProject.fromDirectory(projectDir); + await ensureReadyForELinuxTooling(project); + if (project.hasExampleApp) { + await ensureReadyForELinuxTooling(project.example); + } + } + return result; + } + + /// See: [Template.render] in `template.dart` + @override + Future runCommand() async { + // The template directory that the flutter tools search for available + // templates cannot be overriden because the implementation is private. + // So we have to copy eLinux templates into the directory manually. + final Directory eLinuxTemplates = globals.fs + .directory(Cache.flutterRoot) + .parent + .childDirectory('templates'); + if (!eLinuxTemplates.existsSync()) { + throwToolExit('Could not locate eLinux templates.'); + } + final File eLinuxTemplateManifest = + eLinuxTemplates.childFile('template_manifest.json'); + + final Directory templates = globals.fs + .directory(Cache.flutterRoot) + .childDirectory('packages') + .childDirectory('flutter_tools') + .childDirectory('templates'); + final File templateManifest = templates.childFile('template_manifest.json'); + + // This is required due to: https://github.com/flutter/flutter/pull/59706 + // TODO(swift-kim): Find any better workaround. One option is to override + // renderTemplate() but it may result in additional complexity. + eLinuxTemplateManifest.copySync(templateManifest.path); + + final String backend = stringArg('target-backend-type'); + final List created = []; + try { + for (final Directory projectType + in eLinuxTemplates.listSync().whereType()) { + final Directory sourceRunnerCommon = + projectType.childDirectory('runner'); + if (!sourceRunnerCommon.existsSync()) { + continue; + } + final Directory sourceFlutter = projectType.childDirectory('flutter'); + if (!sourceFlutter.existsSync()) { + continue; + } + + final Directory dest = templates + .childDirectory(projectType.basename) + .childDirectory('elinux.tmpl'); + if (dest.existsSync()) { + dest.deleteSync(recursive: true); + } + + copyDirectory(projectType, dest); + copyDirectory(sourceFlutter, dest.childDirectory('flutter')); + copyDirectory(sourceRunnerCommon, dest.childDirectory('runner')); + if (projectType.basename == 'app') { + final Directory sourceVariation = + projectType.childDirectory('runner_$backend'); + if (!sourceVariation.existsSync()) { + continue; + } + copyDirectory(sourceVariation, dest.childDirectory('runner')); + } + created.add(dest); + } + return await runInternal(); + } finally { + for (final Directory template in created) { + template.deleteSync(recursive: true); + } + } + } +} diff --git a/lib/commands/drive.dart b/lib/commands/drive.dart new file mode 100644 index 0000000..c1845df --- /dev/null +++ b/lib/commands/drive.dart @@ -0,0 +1,21 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/commands/drive.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; + +import '../elinux_plugins.dart'; + +class ELinuxDriveCommand extends DriveCommand with ELinuxExtension { + ELinuxDriveCommand({bool verboseHelp = false}) + : super( + verboseHelp: verboseHelp, + fileSystem: globals.fs, + logger: globals.logger, + platform: globals.platform, + ); +} diff --git a/lib/commands/packages.dart b/lib/commands/packages.dart new file mode 100644 index 0000000..e55275f --- /dev/null +++ b/lib/commands/packages.dart @@ -0,0 +1,106 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/commands/packages.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; + +import '../elinux_plugins.dart'; + +/// This class was copied from [PackagesCommand] to substitute its +/// [PackagesGetCommand] and [PackagesInteractiveGetCommand] subcommands with +/// their ELinux equivalents. We may find a better workaround in the future. +/// +/// Source: [PackagesCommand] in `packages.dart` +class ELinuxPackagesCommand extends FlutterCommand { + ELinuxPackagesCommand() { + addSubcommand(ELinuxPackagesGetCommand('get', false)); + addSubcommand(ELinuxPackagesInteractiveGetCommand('upgrade', + 'Upgrade the current package\'s dependencies to latest versions.')); + addSubcommand(ELinuxPackagesInteractiveGetCommand( + 'add', 'Add a dependency to pubspec.yaml.')); + addSubcommand(ELinuxPackagesInteractiveGetCommand( + 'remove', 'Removes a dependency from the current package.')); + addSubcommand(PackagesTestCommand()); + addSubcommand(PackagesForwardCommand( + 'publish', 'Publish the current package to pub.dartlang.org', + requiresPubspec: true)); + addSubcommand(PackagesForwardCommand( + 'downgrade', 'Downgrade packages in a Flutter project', + requiresPubspec: true)); + addSubcommand(PackagesForwardCommand('deps', 'Print package dependencies', + requiresPubspec: true)); + addSubcommand(PackagesForwardCommand( + 'run', 'Run an executable from a package', + requiresPubspec: true)); + addSubcommand( + PackagesForwardCommand('cache', 'Work with the Pub system cache')); + addSubcommand(PackagesForwardCommand('version', 'Print Pub version')); + addSubcommand(PackagesForwardCommand( + 'uploader', 'Manage uploaders for a package on pub.dev')); + addSubcommand(PackagesForwardCommand('login', 'Log into pub.dev.')); + addSubcommand(PackagesForwardCommand('logout', 'Log out of pub.dev.')); + addSubcommand( + PackagesForwardCommand('global', 'Work with Pub global packages')); + addSubcommand(PackagesForwardCommand( + 'outdated', 'Analyze dependencies to find which ones can be upgraded', + requiresPubspec: true)); + addSubcommand(PackagesPassthroughCommand()); + } + + @override + final String name = 'pub'; + + @override + List get aliases => const ['packages']; + + @override + final String description = 'Commands for managing Flutter packages.'; + + @override + Future runCommand() async => null; +} + +class ELinuxPackagesGetCommand extends PackagesGetCommand + with _PostRunPluginInjection { + ELinuxPackagesGetCommand(String name, bool upgrade) : super(name, upgrade); +} + +class ELinuxPackagesInteractiveGetCommand extends PackagesInteractiveGetCommand + with _PostRunPluginInjection { + ELinuxPackagesInteractiveGetCommand(String commandName, String description) + : super(commandName, description); +} + +mixin _PostRunPluginInjection on FlutterCommand { + /// See: [PackagesGetCommand.runCommand] in `packages.dart` + @override + Future runCommand() async { + final FlutterCommandResult result = await super.runCommand(); + + if (result == FlutterCommandResult.success()) { + final String workingDirectory = + argResults.rest.isNotEmpty ? argResults.rest[0] : null; + final String target = findProjectRoot(globals.fs, workingDirectory); + if (target == null) { + return result; + } + final FlutterProject rootProject = + FlutterProject.fromDirectory(globals.fs.directory(target)); + await ensureReadyForELinuxTooling(rootProject); + if (rootProject.hasExampleApp && + rootProject.example.pubspecFile.existsSync()) { + await ensureReadyForELinuxTooling(rootProject.example); + } + } + + return result; + } +} diff --git a/lib/commands/precache.dart b/lib/commands/precache.dart new file mode 100644 index 0000000..35223a8 --- /dev/null +++ b/lib/commands/precache.dart @@ -0,0 +1,86 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/precache.dart'; +import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; + +import 'package:meta/meta.dart'; + +import '../elinux_cache.dart'; + +class ELinuxPrecacheCommand extends PrecacheCommand { + ELinuxPrecacheCommand({ + bool verboseHelp = false, + @required Cache cache, + @required Platform platform, + @required Logger logger, + @required FeatureFlags featureFlags, + }) : _cache = cache, + _platform = platform, + super( + verboseHelp: verboseHelp, + cache: cache, + platform: platform, + logger: logger, + featureFlags: featureFlags, + ) { + argParser.addFlag( + 'elinux', + negatable: true, + defaultsTo: false, + help: 'Precache artifacts for Embedded Linux development.', + ); + } + + final Cache _cache; + final Platform _platform; + + bool get _includeOtherPlatforms => + boolArg('android') || + DevelopmentArtifact.values.any((DevelopmentArtifact artifact) => + boolArg(artifact.name) && argResults.wasParsed(artifact.name)); + + @override + Future runCommand() async { + final bool includeAllPlatforms = boolArg('all-platforms'); + final bool includeELinux = boolArg('elinux'); + final bool includeDefaults = !includeELinux && !_includeOtherPlatforms; + + const String elinuxStampName = 'elinux-sdk'; + + // Re-lock the cache. + if (_platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { + await _cache.lock(); + } + + if (includeAllPlatforms || includeDefaults || includeELinux) { + if (boolArg('force')) { + _cache.setStampFor(elinuxStampName, ''); + } + await _cache.updateAll({ + ELinuxDevelopmentArtifact.elinux, + }); + } + + if (includeAllPlatforms || includeDefaults || _includeOtherPlatforms) { + // If the '--force' option is used, the super.runCommand() will delete + // the elinux's stamp file. It should be restored. + final String elinuxStamp = _cache.getStampFor(elinuxStampName); + final FlutterCommandResult result = await super.runCommand(); + if (elinuxStamp != null) { + _cache.setStampFor(elinuxStampName, elinuxStamp); + } + return result; + } + + return FlutterCommandResult.success(); + } +} diff --git a/lib/commands/run.dart b/lib/commands/run.dart new file mode 100644 index 0000000..b90d9d3 --- /dev/null +++ b/lib/commands/run.dart @@ -0,0 +1,31 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/run.dart'; + +import '../elinux_cache.dart'; +import '../elinux_plugins.dart'; + +class ELinuxRunCommand extends RunCommand with ELinuxExtension { + ELinuxRunCommand({bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + argParser.addOption( + 'target-backend-type', + defaultsTo: 'wayland', + allowed: ['wayland', 'gbm', 'eglstream', 'x11'], + help: 'Target backend type that the app will run on devices.', + ); + } + + @override + Future> get requiredArtifacts async => + { + DevelopmentArtifact.androidGenSnapshot, + ELinuxDevelopmentArtifact.elinux, + }; +} diff --git a/lib/commands/test.dart b/lib/commands/test.dart new file mode 100644 index 0000000..58cf60d --- /dev/null +++ b/lib/commands/test.dart @@ -0,0 +1,15 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/commands/test.dart'; + +import '../elinux_plugins.dart'; + +class ELinuxTestCommand extends TestCommand with ELinuxExtension { + ELinuxTestCommand({bool verboseHelp = false}) + : super(verboseHelp: verboseHelp); +} diff --git a/lib/elinux_build_target.dart b/lib/elinux_build_target.dart new file mode 100644 index 0000000..6da7b7d --- /dev/null +++ b/lib/elinux_build_target.dart @@ -0,0 +1,436 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'dart:io'; + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/build_system/depfile.dart'; +import 'package:flutter_tools/src/build_system/exceptions.dart'; +import 'package:flutter_tools/src/build_system/source.dart'; +import 'package:flutter_tools/src/build_system/targets/android.dart'; +import 'package:flutter_tools/src/build_system/targets/assets.dart'; +import 'package:flutter_tools/src/build_system/targets/common.dart'; +import 'package:flutter_tools/src/build_system/targets/icon_tree_shaker.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/cmake.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; + +import 'elinux_builder.dart'; +import 'elinux_cmake_project.dart'; +import 'elinux_plugins.dart'; + +/// Prepares the pre-built flutter bundle. +/// +/// Source: [AndroidAssetBundle] in `android.dart` +abstract class ELinuxAssetBundle extends Target { + const ELinuxAssetBundle(); + + @override + String get name => 'elinux_asset_bundle'; + + @override + List get inputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + ...IconTreeShaker.inputs, + ]; + + @override + List get outputs => const []; + + @override + List get depfiles => [ + 'flutter_assets.d', + ]; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; + + @override + Future build(Environment environment) async { + if (environment.defines[kBuildMode] == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = + getBuildModeForName(environment.defines[kBuildMode]); + final Directory outputDirectory = environment.outputDir + .childDirectory('flutter_assets') + ..createSync(recursive: true); + + // Only copy the prebuilt runtimes and kernel blob in debug mode. + if (buildMode == BuildMode.debug) { + final String vmSnapshotData = environment.artifacts + .getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); + final String isolateSnapshotData = environment.artifacts + .getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); + environment.buildDir + .childFile('app.dill') + .copySync(outputDirectory.childFile('kernel_blob.bin').path); + environment.fileSystem + .file(vmSnapshotData) + .copySync(outputDirectory.childFile('vm_snapshot_data').path); + environment.fileSystem + .file(isolateSnapshotData) + .copySync(outputDirectory.childFile('isolate_snapshot_data').path); + } + final Depfile assetDepfile = await copyAssets( + environment, + outputDirectory, + targetPlatform: null, // corresponds to flutter-tester + ); + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + assetDepfile, + environment.buildDir.childFile('flutter_assets.d'), + ); + } +} + +/// Source: [DebugAndroidApplication] in `android.dart` +class DebugELinuxApplication extends ELinuxAssetBundle { + DebugELinuxApplication(this.buildInfo); + + final ELinuxBuildInfo buildInfo; + + @override + String get name => 'debug_elinux_application'; + + @override + List get inputs => [ + ...super.inputs, + const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), + const Source.artifact(Artifact.isolateSnapshotData, + mode: BuildMode.debug), + ]; + + @override + List get outputs => [ + ...super.outputs, + const Source.pattern('{OUTPUT_DIR}/flutter_assets/vm_snapshot_data'), + const Source.pattern( + '{OUTPUT_DIR}/flutter_assets/isolate_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'), + ]; + + @override + List get dependencies => [ + ...super.dependencies, + ELinuxPlugins(buildInfo), + ]; +} + +/// See: [ReleaseAndroidApplication] in `android.dart` +class ReleaseELinuxApplication extends ELinuxAssetBundle { + ReleaseELinuxApplication(this.buildInfo); + + final ELinuxBuildInfo buildInfo; + + @override + String get name => 'release_elinux_application'; + + @override + List get dependencies => [ + ...super.dependencies, + ELinuxAotElf(), + ELinuxPlugins(buildInfo), + ]; +} + +/// Compiles eLinux native plugins into a single shared object. +class ELinuxPlugins extends Target { + ELinuxPlugins(this.buildInfo); + + final ELinuxBuildInfo buildInfo; + + @override + String get name => 'elinux_plugins'; + + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/../lib/elinux_build_target.dart'), + Source.pattern('{PROJECT_DIR}/.packages'), + ]; + + @override + List get outputs => const []; + + @override + List get depfiles => [ + 'elinux_plugins.d', + ]; + + @override + List get dependencies => const []; + + @override + Future build(Environment environment) async { + // todo: add plugin build support. + } +} + +/// Generates an AOT snapshot (app.so) of the Dart code. +/// +/// Source: [AotElfRelease] in `common.dart` +class ELinuxAotElf extends AotElfBase { + ELinuxAotElf(); + + @override + String get name => 'elinux_aot_elf'; + + @override + List get inputs => [ + const Source.pattern('{BUILD_DIR}/app.dill'), + const Source.hostArtifact(HostArtifact.engineDartBinary), + const Source.artifact(Artifact.skyEnginePath), + // Any type of gen_snapshot is applicable here because engine artifacts + // are assumed to be updated at once, not one by one for each platform + // or build mode. + const Source.artifact(Artifact.genSnapshot, mode: BuildMode.release), + ]; + + @override + List get outputs => const [ + Source.pattern('{BUILD_DIR}/app.so'), + ]; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} + +class NativeBundle { + NativeBundle(this.buildInfo, this.targetFile); + + final ELinuxBuildInfo buildInfo; + final String targetFile; + + final ProcessUtils _processUtils = ProcessUtils( + logger: globals.logger, processManager: globals.processManager); + + Future build(Environment environment) async { + final FlutterProject project = + FlutterProject.fromDirectory(environment.projectDir); + final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project); + + // Clean up the intermediate and output directories. + final Directory eLinuxDir = eLinuxProject.editableDirectory; + + final BuildMode buildMode = buildInfo.buildInfo.mode; + final String buildConfig = buildMode.isPrecompiled ? 'release' : 'debug'; + final Directory outputDir = environment.outputDir + .childDirectory(buildInfo.targetArch) + .childDirectory(buildConfig); + if (outputDir.existsSync()) { + outputDir.deleteSync(recursive: true); + } + outputDir.createSync(recursive: true); + + final Directory outputBundleDir = outputDir.childDirectory('bundle'); + if (outputBundleDir.existsSync()) { + outputBundleDir.deleteSync(recursive: true); + } + outputBundleDir.createSync(recursive: true); + + final Directory outputBundleLibDir = outputBundleDir.childDirectory('lib'); + if (outputBundleLibDir.existsSync()) { + outputBundleLibDir.deleteSync(recursive: true); + } + outputBundleLibDir.createSync(recursive: true); + + final Directory outputBundleDataDir = + outputBundleDir.childDirectory('data'); + if (outputBundleDataDir.existsSync()) { + outputBundleDataDir.deleteSync(recursive: true); + } + outputBundleDataDir.createSync(recursive: true); + + // Copy necessary files + final Directory engineDir = + _getEngineArtifactsDirectory(buildInfo.targetArch, buildMode); + final Directory commonDir = + engineDir.parent.childDirectory('elinux-common'); + final File engineBinary = engineDir.childFile('libflutter_engine.so'); + final File embedder = + engineDir.childFile(buildInfo.targetBackendType == 'gbm' + ? 'libflutter_elinux_gbm.so' + : buildInfo.targetBackendType == 'eglstream' + ? 'libflutter_elinux_eglstream.so' + : buildInfo.targetBackendType == 'x11' + ? 'libflutter_elinux_x11.so' + : 'libflutter_elinux_wayland.so'); + final Directory clientWrapperDir = + commonDir.childDirectory('cpp_client_wrapper'); + + final Directory pluginsDir = + environment.buildDir.childDirectory('elinux_plugins'); + final File pluginsLib = pluginsDir.childFile('libflutter_plugins.so'); + if (pluginsLib.existsSync()) { + pluginsLib + .copySync(outputBundleLibDir.childFile(pluginsLib.basename).path); + } + final Directory pluginsUserLibDir = pluginsDir.childDirectory('lib'); + if (pluginsUserLibDir.existsSync()) { + pluginsUserLibDir.listSync().whereType().forEach((File lib) => + lib.copySync(outputBundleLibDir.childFile(lib.basename).path)); + } + + final Directory flutterDir = eLinuxDir.childDirectory('flutter'); + final Directory flutterEphemeralDir = + flutterDir.childDirectory('ephemeral'); + + // Copy necessary files. + { + if (flutterEphemeralDir.existsSync()) { + flutterEphemeralDir.deleteSync(recursive: true); + } + flutterEphemeralDir.createSync(recursive: true); + flutterEphemeralDir + .childDirectory('cpp_client_wrapper') + .createSync(recursive: true); + + copyDirectory( + clientWrapperDir, + flutterEphemeralDir.childDirectory('cpp_client_wrapper'), + ); + + commonDir.listSync().whereType().forEach((File lib) => + lib.copySync(flutterEphemeralDir.childFile(lib.basename).path)); + + engineBinary + .copySync(flutterEphemeralDir.childFile(engineBinary.basename).path); + embedder.copySync(flutterEphemeralDir.childFile(embedder.basename).path); + + final File icuData = + commonDir.childDirectory('icu').childFile('icudtl.dat'); + icuData.copySync(flutterEphemeralDir.childFile(icuData.basename).path); + + if (buildMode.isPrecompiled) { + final File aotSharedLib = environment.buildDir.childFile('app.so'); + aotSharedLib.copySync(flutterEphemeralDir.childFile('libapp.so').path); + } + } + + // Build the environment that needs to be set for the re-entrant flutter build + // step. + { + final Map environment = { + if (targetFile != null) 'FLUTTER_TARGET': targetFile, + ...buildInfo.buildInfo.toEnvironmentConfig(), + }; + if (globals.artifacts is LocalEngineArtifacts) { + final LocalEngineArtifacts localEngineArtifacts = + globals.artifacts as LocalEngineArtifacts; + final String engineOutPath = localEngineArtifacts.engineOutPath; + environment['FLUTTER_ENGINE'] = + globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath)); + environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath); + } + writeGeneratedCmakeConfig(Cache.flutterRoot, eLinuxProject, environment); + + //createPluginSymlinks(eLinuxProject.parent); + } + + // Run the native build. + final String cmakeBuildType = buildMode.isPrecompiled ? 'Release' : 'Debug'; + RunResult result = await _processUtils.run( + [ + 'cmake', + '-DCMAKE_BUILD_TYPE=$cmakeBuildType', + '-DFLUTTER_TARGET_BACKEND_TYPE=${buildInfo.targetBackendType}', + eLinuxDir.path, + ], + workingDirectory: outputDir.path, + environment: {'CC': 'clang', 'CXX': 'clang++'}, + ); + if (result.exitCode != 0) { + throwToolExit('Failed to cmake:\n$result'); + } + + result = await _processUtils.run( + ['cmake', '--build', '.'], + workingDirectory: outputDir.path, + ); + if (result.exitCode != 0) { + throwToolExit('Failed to cmake build:\n$result'); + } + + // Create flutter app's bunle. + result = await _processUtils.run( + ['cmake', '--install', '.'], + workingDirectory: outputDir.path, + ); + if (result.exitCode != 0) { + throwToolExit('Failed to cmake install:\n$result'); + } + { + final Directory flutterAssetsDir = + outputBundleDataDir.childDirectory('flutter_assets'); + copyDirectory( + environment.outputDir.childDirectory('flutter_assets'), + flutterAssetsDir, + ); + } + } +} + +/// Converts [targetArch] to an arch name that corresponds to the `BUILD_ARCH` +/// value used by the eLinux native builder. +String getELinuxBuildArch(String targetArch) { + switch (targetArch) { + case 'arm': + return 'armel'; + case 'arm64': + return 'aarch64'; + case 'x86': + return 'i586'; + default: + return targetArch; + } +} + +/// On non-Windows, returns [path] unchanged. +/// +/// On Windows, converts Windows-style [path] (e.g. 'C:\x\y') into Unix path +/// ('/c/x/y') and returns. +String getUnixPath(String path) { + if (Platform.isWindows) { + path = path.replaceAll(r'\', '/'); + if (path.startsWith(':', 1)) { + path = '/${path[0].toLowerCase()}${path.substring(2)}'; + } + } + return path; +} + +/// On non-Windows, returns the PATH environment variable. +/// +/// On Windows, appends the msys2 executables directory to PATH and returns. +String getDefaultPathVariable() { + final Map variables = globals.platform.environment; + return variables.containsKey('PATH') ? variables['PATH'] : ''; +} + +/// See: [CachedArtifacts._getEngineArtifactsPath] +Directory _getEngineArtifactsDirectory(String arch, BuildMode mode) { + assert(mode != null, 'Need to specify a build mode.'); + return globals.cache + .getArtifactDirectory('engine') + .childDirectory('elinux-$arch-${mode.name}'); +} diff --git a/lib/elinux_builder.dart b/lib/elinux_builder.dart new file mode 100644 index 0000000..613d6d2 --- /dev/null +++ b/lib/elinux_builder.dart @@ -0,0 +1,150 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/android/android_builder.dart'; +import 'package:flutter_tools/src/android/gradle.dart'; +import 'package:flutter_tools/src/base/analyze_size.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/assemble.dart'; +import 'package:flutter_tools/src/commands/build_ios_framework.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/linux/build_linux.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:meta/meta.dart'; + +import 'elinux_build_target.dart'; +import 'elinux_cmake_project.dart'; + +/// The define to control what eLinux device is built for. +const String kTargetBackendType = 'TargetBackendType'; + +/// See: [AndroidBuildInfo] in `build_info.dart` +class ELinuxBuildInfo { + const ELinuxBuildInfo( + this.buildInfo, { + @required this.targetArch, + @required this.targetBackendType, + }) : assert(targetArch != null), + assert(targetBackendType != null); + + final BuildInfo buildInfo; + final String targetArch; + final String targetBackendType; +} + +/// See: +/// - [AndroidBuilder] in `android_builder.dart` +/// - [AndroidGradleBuilder.buildGradleApp] in `gradle.dart` +/// - [BuildIOSFrameworkCommand._produceAppFramework] in `build_ios_framework.dart` (build target) +/// - [AssembleCommand.runCommand] in `assemble.dart` (performance measurement) +/// - [buildLinux] in `build_linux.dart` (code size) +class ELinuxBuilder { + static Future buildBundle({ + @required FlutterProject project, + @required ELinuxBuildInfo eLinuxBuildInfo, + @required String targetFile, + SizeAnalyzer sizeAnalyzer, + }) async { + final ELinuxProject elinuxProject = ELinuxProject.fromFlutter(project); + if (!elinuxProject.existsSync()) { + throwToolExit( + 'This project is not configured for eLinux.\n' + 'To fix this problem, create a new project by running `flutter-elinux create `.', + ); + } + + final Directory outputDir = + project.directory.childDirectory('build').childDirectory('elinux'); + final BuildInfo buildInfo = eLinuxBuildInfo.buildInfo; + final String buildModeName = getNameForBuildMode(buildInfo.mode); + // Used by AotElfBase to generate an AOT snapshot. + final String targetPlatform = getNameForTargetPlatform( + _getTargetPlatformForArch(eLinuxBuildInfo.targetArch)); + + final Environment environment = Environment( + projectDir: project.directory, + outputDir: outputDir, + buildDir: project.dartTool.childDirectory('flutter_build'), + cacheDir: globals.cache.getRoot(), + flutterRootDir: globals.fs.directory(Cache.flutterRoot), + engineVersion: globals.flutterVersion.engineRevision, + generateDartPluginRegistry: true, + defines: { + kTargetFile: targetFile, + kBuildMode: buildModeName, + kTargetPlatform: targetPlatform, + kDartObfuscation: buildInfo.dartObfuscation.toString(), + kSplitDebugInfo: buildInfo.splitDebugInfoPath, + kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), + kTrackWidgetCreation: buildInfo.trackWidgetCreation.toString(), + kCodeSizeDirectory: buildInfo.codeSizeDirectory, + if (buildInfo.dartDefines?.isNotEmpty ?? false) + kDartDefines: encodeDartDefines(buildInfo.dartDefines), + if (buildInfo.extraGenSnapshotOptions?.isNotEmpty ?? false) + kExtraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions.join(','), + if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) + kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(','), + kTargetBackendType: eLinuxBuildInfo.targetBackendType, + }, + inputs: { + kBundleSkSLPath: buildInfo.bundleSkSLPath, + }, + artifacts: globals.artifacts, + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + platform: globals.platform, + ); + + final Target target = buildInfo.isDebug + ? DebugELinuxApplication(eLinuxBuildInfo) + : ReleaseELinuxApplication(eLinuxBuildInfo); + + final Status status = globals.logger.startProgress( + 'Building an eLinux application with ${eLinuxBuildInfo.targetBackendType} backend in $buildModeName mode for ${eLinuxBuildInfo.targetArch} target...'); + try { + final BuildResult result = + await globals.buildSystem.build(target, environment); + if (!result.success) { + for (final ExceptionMeasurement measurement + in result.exceptions.values) { + globals.printError(measurement.exception.toString()); + } + throwToolExit('The build failed.'); + } + + // These pseudo targets cannot be skipped and should be invoked whenever + // the build is run. + await NativeBundle(eLinuxBuildInfo, targetFile).build(environment); + + if (buildInfo.performanceMeasurementFile != null) { + final File outFile = + globals.fs.file(buildInfo.performanceMeasurementFile); + // ignore: invalid_use_of_visible_for_testing_member + writePerformanceData(result.performance.values, outFile); + } + } finally { + status.stop(); + } + } +} + +/// See: [getTargetPlatformForName] in `build_info.dart` +TargetPlatform _getTargetPlatformForArch(String arch) { + switch (arch) { + case 'arm64': + return TargetPlatform.android_arm64; + default: + return TargetPlatform.android_x64; + } +} diff --git a/lib/elinux_cache.dart b/lib/elinux_cache.dart new file mode 100644 index 0000000..cf7af56 --- /dev/null +++ b/lib/elinux_cache.dart @@ -0,0 +1,133 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart' show OperatingSystemUtils; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/flutter_cache.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; + +import 'package:meta/meta.dart'; + +/// See: [DevelopmentArtifact] in `cache.dart` +class ELinuxDevelopmentArtifact implements DevelopmentArtifact { + const ELinuxDevelopmentArtifact._(this.name, this.feature); + + @override + final String name; + + @override + final Feature feature; + + static const DevelopmentArtifact elinux = + ELinuxDevelopmentArtifact._('elinux', null); +} + +/// Extends [FlutterCache] to register [ELinuxEngineArtifacts]. +/// +/// See: [FlutterCache] in `flutter_cache.dart` +class ELinuxFlutterCache extends FlutterCache { + ELinuxFlutterCache({ + @required Logger logger, + @required FileSystem fileSystem, + @required Platform platform, + @required OperatingSystemUtils osUtils, + }) : super( + logger: logger, + fileSystem: fileSystem, + platform: platform, + osUtils: osUtils) { + registerArtifact(ELinuxEngineArtifacts(this, platform: platform)); + } +} + +class ELinuxEngineArtifacts extends EngineCachedArtifact { + ELinuxEngineArtifacts( + Cache cache, { + @required Platform platform, + }) : _platform = platform, + super( + 'elinux-sdk', + cache, + ELinuxDevelopmentArtifact.elinux, + ); + + final Platform _platform; + + @override + String get version { + final File versionFile = globals.fs + .directory(Cache.flutterRoot) + .parent + .childDirectory('bin') + .childDirectory('internal') + .childFile('engine.version'); + return versionFile.existsSync() + ? versionFile.readAsStringSync().trim() + : null; + } + + String get shortVersion { + if (version != null && version.length >= 10) { + return version.substring(0, 10); + } + return version; + } + + /// See: [Cache.storageBaseUrl] in `cache.dart` + String get engineBaseUrl { + final String overrideUrl = _platform.environment['ELINUX_ENGINE_BASE_URL']; + if (overrideUrl == null) { + return 'https://github.com/sony/flutter-embedded-linux/releases'; + } + try { + Uri.parse(overrideUrl); + } on FormatException catch (err) { + throwToolExit('"ELINUX_ENGINE_BASE_URL" contains an invalid URI:\n$err'); + } + return overrideUrl; + } + + @override + List> getBinaryDirs() => >[ + ['elinux-common', 'elinux-common.zip'], + ['elinux-arm64-debug', 'elinux-arm64-debug.zip'], + ['elinux-arm64-profile', 'elinux-arm64-profile.zip'], + ['elinux-arm64-release', 'elinux-arm64-release.zip'], + ['elinux-x64-debug', 'elinux-x64-debug.zip'], + ['elinux-x64-profile', 'elinux-x64-profile.zip'], + ['elinux-x64-release', 'elinux-x64-release.zip'], + ]; + + @override + List getLicenseDirs() => const []; + + @override + List getPackageDirs() => const []; + + @override + Future updateInner( + ArtifactUpdater artifactUpdater, + FileSystem fileSystem, + OperatingSystemUtils operatingSystemUtils, + ) async { + final String downloadUrl = '$engineBaseUrl/download/$shortVersion'; + for (final List toolsDir in getBinaryDirs()) { + final String cacheDir = toolsDir[0]; + final String urlPath = toolsDir[1]; + await artifactUpdater.downloadZipArchive( + 'Downloading $cacheDir tools...', + Uri.parse('$downloadUrl/$urlPath'), + location.childDirectory(cacheDir), + ); + } + } +} diff --git a/lib/elinux_cmake_project.dart b/lib/elinux_cmake_project.dart new file mode 100644 index 0000000..cd84d4f --- /dev/null +++ b/lib/elinux_cmake_project.dart @@ -0,0 +1,54 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/project.dart'; + +import 'elinux_plugins.dart'; + +/// The eLinux sub project. +class ELinuxProject extends FlutterProjectPlatform + implements CmakeBasedProject { + ELinuxProject.fromFlutter(this.parent); + + @override + final FlutterProject parent; + + @override + String get pluginConfigKey => ELinuxPlugin.kConfigKey; + + String get _childDirectory => 'elinux'; + + @override + bool existsSync() => editableDirectory.existsSync() && cmakeFile.existsSync(); + + @override + File get cmakeFile => editableDirectory.childFile('CMakeLists.txt'); + + @override + File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt'); + + @override + File get generatedCmakeConfigFile => + ephemeralDirectory.childFile('generated_config.cmake'); + + @override + File get generatedPluginCmakeFile => + managedDirectory.childFile('generated_plugins.cmake'); + + @override + Directory get pluginSymlinkDirectory => + ephemeralDirectory.childDirectory('.plugin_symlinks'); + + Directory get editableDirectory => + parent.directory.childDirectory(_childDirectory); + + Directory get managedDirectory => editableDirectory.childDirectory('flutter'); + + Directory get ephemeralDirectory => + managedDirectory.childDirectory('ephemeral'); + + Future ensureReadyForPlatformSpecificTooling() async {} +} diff --git a/lib/elinux_device.dart b/lib/elinux_device.dart new file mode 100644 index 0000000..41ec210 --- /dev/null +++ b/lib/elinux_device.dart @@ -0,0 +1,356 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_tools/src/android/android_device.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/device_port_forwarder.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/protocol_discovery.dart'; + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import 'elinux_builder.dart'; +import 'elinux_package.dart'; + +/// eLinux device implementation. +/// +/// See: [DesktopDevice] in `desktop_device.dart` +class ELinuxDevice extends Device { + ELinuxDevice( + String id, { + @required bool desktop, + @required String backendType, + @required String targetArch, + @required Logger logger, + @required ProcessManager processManager, + @required OperatingSystemUtils operatingSystemUtils, + }) : _desktop = desktop, + _backendType = backendType, + _targetArch = targetArch, + _logger = logger, + _processManager = processManager, + _operatingSystemUtils = operatingSystemUtils, + super(id, + category: Category.desktop, + platformType: PlatformType.custom, + ephemeral: true); + + final bool _desktop; + final String _backendType; + final String _targetArch; + final Logger _logger; + final ProcessManager _processManager; + final OperatingSystemUtils _operatingSystemUtils; + final Set _runningProcesses = {}; + final ELinuxLogReader _logReader = ELinuxLogReader(); + + @override + Future get isLocalEmulator async => false; + + @override + Future get emulatorId async => null; + + @override + Future get targetPlatform async { + // Use tester as a platform identifer for eLinux. + // There's currently no other choice because getNameForTargetPlatform() + // throws an error for unknown platform types. + return TargetPlatform.tester; + } + + @override + bool supportsRuntimeMode(BuildMode buildMode) => + buildMode != BuildMode.jitRelease; + + @override + Future get sdkNameAndVersion async => _operatingSystemUtils.name; + + @override + String get name => 'eLinux'; + + @override + Future isAppInstalled(ELinuxApp app, {String userIdentifier}) async { + return true; + } + + @override + Future isLatestBuildInstalled(ELinuxApp app) async { + return true; + } + + @override + Future installApp(ELinuxApp app, {String userIdentifier}) async { + return true; + } + + @override + Future uninstallApp(ELinuxApp app, {String userIdentifier}) async { + return true; + } + + /// Source: [AndroidDevice.startApp] in `android_device.dart` + @override + Future startApp( + ELinuxApp package, { + String mainPath, + String route, + DebuggingOptions debuggingOptions, + Map platformArgs, + bool prebuiltApplication = false, + bool ipv6 = false, + String userIdentifier, + }) async { + if (!prebuiltApplication) { + _logger.printTrace('Building app'); + await buildForDevice( + package, + buildInfo: debuggingOptions.buildInfo, + mainPath: mainPath, + ); + } + + // Ensure that the executable is locatable. + final BuildMode buildMode = debuggingOptions?.buildInfo?.mode; + final bool traceStartup = platformArgs['trace-startup'] as bool ?? false; + final String executable = executablePathForDevice(package, buildMode); + const String executableOptions = '--bundle=./'; + if (executable == null) { + _logger.printError('Unable to find executable to run'); + return LaunchResult.failed(); + } + + final Process process = await _processManager.start( + [ + executable, + executableOptions, + if (_desktop && _backendType == 'wayland') '-d', + ...?debuggingOptions?.dartEntrypointArgs, + ], + environment: _computeEnvironment(debuggingOptions, traceStartup, route), + ); + _runningProcesses.add(process); + unawaited(process.exitCode.then((_) => _runningProcesses.remove(process))); + + _logReader.initializeProcess(process); + if (debuggingOptions?.buildInfo?.isRelease == true) { + return LaunchResult.succeeded(); + } + final ProtocolDiscovery observatoryDiscovery = + ProtocolDiscovery.observatory( + _logReader, + devicePort: debuggingOptions?.deviceVmServicePort, + hostPort: debuggingOptions?.hostVmServicePort, + ipv6: ipv6, + logger: _logger, + ); + try { + final Uri observatoryUri = await observatoryDiscovery.uri; + if (observatoryUri != null) { + onAttached(package, buildMode, process); + return LaunchResult.succeeded(observatoryUri: observatoryUri); + } + _logger.printError( + 'Error waiting for a debug connection: ' + 'The log reader stopped unexpectedly.', + ); + } on Exception catch (error) { + _logger.printError('Error waiting for a debug connection: $error'); + } finally { + await observatoryDiscovery.cancel(); + } + return LaunchResult.failed(); + } + + @override + Future stopApp(ELinuxApp app, {String userIdentifier}) async { + bool succeeded = true; + // Walk a copy of _runningProcesses, since the exit handler removes from the + // set. + for (final Process process in Set.of(_runningProcesses)) { + succeeded &= _processManager.killPid(process.pid); + } + return succeeded; + } + + @override + void clearLogs() {} + + @override + FutureOr getLogReader({ + ELinuxApp app, + bool includePastLogs = false, + }) => + _logReader; + + @override + DevicePortForwarder get portForwarder => null; + + @override + bool isSupported() => true; + + @override + bool get supportsScreenshot => false; + + @override + bool isSupportedForProject(FlutterProject flutterProject) { + return flutterProject.isModule && + flutterProject.directory.childDirectory('elinux').existsSync(); + } + + @override + Future dispose() async {} + + Future buildForDevice( + ELinuxApp package, { + String mainPath, + BuildInfo buildInfo, + }) async { + final FlutterProject project = FlutterProject.current(); + final ELinuxBuildInfo eLinuxBuildInfo = ELinuxBuildInfo( + buildInfo, + targetArch: _targetArch, + targetBackendType: _backendType, + ); + await ELinuxBuilder.buildBundle( + project: project, + targetFile: mainPath, + eLinuxBuildInfo: eLinuxBuildInfo, + ); + package = ELinuxApp.fromELinuxProject(project); + } + + String executablePathForDevice(ELinuxApp package, BuildMode buildMode) { + return package.executable(buildMode, _targetArch); + } + + void onAttached(ELinuxApp package, BuildMode buildMode, Process process) {} + + /// Computes a set of environment variables used to pass debugging information + /// to the engine without interfering with application level command line + /// arguments. + /// + /// The format of the environment variables is: + /// * FLUTTER_ENGINE_SWITCHES to the number of switches. + /// * FLUTTER_ENGINE_SWITCH_ (indexing from 1) to the individual switches. + Map _computeEnvironment( + DebuggingOptions debuggingOptions, bool traceStartup, String route) { + int flags = 0; + final Map environment = {}; + + void addFlag(String value) { + flags += 1; + environment['FLUTTER_ENGINE_SWITCH_$flags'] = value; + } + + void finish() { + environment['FLUTTER_ENGINE_SWITCHES'] = flags.toString(); + } + + addFlag('enable-dart-profiling=true'); + addFlag('enable-background-compilation=true'); + + if (traceStartup) { + addFlag('trace-startup=true'); + } + if (route != null) { + addFlag('route=$route'); + } + if (debuggingOptions.enableSoftwareRendering) { + addFlag('enable-software-rendering=true'); + } + if (debuggingOptions.skiaDeterministicRendering) { + addFlag('skia-deterministic-rendering=true'); + } + if (debuggingOptions.traceSkia) { + addFlag('trace-skia=true'); + } + if (debuggingOptions.traceAllowlist != null) { + addFlag('trace-allowlist=${debuggingOptions.traceAllowlist}'); + } + if (debuggingOptions.traceSkiaAllowlist != null) { + addFlag('trace-skia-allowlist=${debuggingOptions.traceSkiaAllowlist}'); + } + if (debuggingOptions.traceSystrace) { + addFlag('trace-systrace=true'); + } + if (debuggingOptions.endlessTraceBuffer) { + addFlag('endless-trace-buffer=true'); + } + if (debuggingOptions.dumpSkpOnShaderCompilation) { + addFlag('dump-skp-on-shader-compilation=true'); + } + if (debuggingOptions.cacheSkSL) { + addFlag('cache-sksl=true'); + } + if (debuggingOptions.purgePersistentCache) { + addFlag('purge-persistent-cache=true'); + } + // Options only supported when there is a VM Service connection between the + // tool and the device, usually in debug or profile mode. + if (debuggingOptions.debuggingEnabled) { + if (debuggingOptions.deviceVmServicePort != null) { + addFlag('observatory-port=${debuggingOptions.deviceVmServicePort}'); + } + if (debuggingOptions.buildInfo.isDebug) { + addFlag('enable-checked-mode=true'); + addFlag('verify-entry-points=true'); + } + if (debuggingOptions.startPaused) { + addFlag('start-paused=true'); + } + if (debuggingOptions.disableServiceAuthCodes) { + addFlag('disable-service-auth-codes=true'); + } + final String dartVmFlags = computeDartVmFlags(debuggingOptions); + if (dartVmFlags.isNotEmpty) { + addFlag('dart-flags=$dartVmFlags'); + } + if (debuggingOptions.useTestFonts) { + addFlag('use-test-fonts=true'); + } + if (debuggingOptions.verboseSystemLogs) { + addFlag('verbose-logging=true'); + } + } + finish(); + return environment; + } +} + +class ELinuxLogReader extends DeviceLogReader { + final StreamController> _inputController = + StreamController>.broadcast(); + + void initializeProcess(Process process) { + process.stdout.listen(_inputController.add); + process.stderr.listen(_inputController.add); + process.exitCode.whenComplete(_inputController.close); + } + + @override + Stream get logLines { + return _inputController.stream + .transform(utf8.decoder) + .transform(const LineSplitter()); + } + + @override + String get name => 'eLinux'; + + @override + void dispose() {} +} diff --git a/lib/elinux_device_discovery.dart b/lib/elinux_device_discovery.dart new file mode 100644 index 0000000..5ea68e7 --- /dev/null +++ b/lib/elinux_device_discovery.dart @@ -0,0 +1,132 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:flutter_tools/src/android/android_device_discovery.dart'; +import 'package:flutter_tools/src/android/android_workflow.dart'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/context_runner.dart'; +import 'package:flutter_tools/src/custom_devices/custom_devices_config.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/flutter_device_manager.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/macos/macos_workflow.dart'; +import 'package:flutter_tools/src/windows/uwptool.dart'; +import 'package:flutter_tools/src/windows/windows_workflow.dart'; +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import 'elinux_device.dart'; +import 'elinux_doctor.dart'; + +/// An extended [FlutterDeviceManager] for managing eLinux devices. +class ELinuxDeviceManager extends FlutterDeviceManager { + /// Source: [runInContext] in `context_runner.dart` + ELinuxDeviceManager() + : super( + logger: globals.logger, + processManager: globals.processManager, + platform: globals.platform, + androidSdk: globals.androidSdk, + iosSimulatorUtils: globals.iosSimulatorUtils, + featureFlags: featureFlags, + fileSystem: globals.fs, + iosWorkflow: globals.iosWorkflow, + artifacts: globals.artifacts, + flutterVersion: globals.flutterVersion, + androidWorkflow: androidWorkflow, + config: globals.config, + fuchsiaWorkflow: fuchsiaWorkflow, + xcDevice: globals.xcdevice, + userMessages: globals.userMessages, + windowsWorkflow: windowsWorkflow, + macOSWorkflow: context.get(), + operatingSystemUtils: globals.os, + terminal: globals.terminal, + customDevicesConfig: CustomDevicesConfig( + fileSystem: globals.fs, + logger: globals.logger, + platform: globals.platform, + ), + uwptool: UwpTool( + artifacts: globals.artifacts, + logger: globals.logger, + processManager: globals.processManager, + ), + ); + + final ELinuxDeviceDiscovery _eLinuxDeviceDiscovery = ELinuxDeviceDiscovery( + eLinuxWorkflow: eLinuxWorkflow, + logger: globals.logger, + processManager: globals.processManager, + ); + + @override + List get deviceDiscoverers => [ + ...super.deviceDiscoverers, + _eLinuxDeviceDiscovery, + ]; +} + +/// Device discovery for eLinux devices. +/// +/// Source: [AndroidDevices] in `android_device_discovery.dart` +class ELinuxDeviceDiscovery extends PollingDeviceDiscovery { + ELinuxDeviceDiscovery({ + @required ELinuxWorkflow eLinuxWorkflow, + @required ProcessManager processManager, + @required Logger logger, + }) : _eLinuxWorkflow = eLinuxWorkflow, + _logger = logger, + _processManager = processManager, + super('eLinux devices'); + + final ELinuxWorkflow _eLinuxWorkflow; + final Logger _logger; + final ProcessManager _processManager; + + @override + bool get supportsPlatform => _eLinuxWorkflow.appliesToHostPlatform; + + @override + bool get canListAnything => _eLinuxWorkflow.canListDevices; + + @override + Future> pollingGetDevices({Duration timeout}) async { + if (!canListAnything) { + return const []; + } + + return [ + ELinuxDevice('eLinux', + desktop: true, + targetArch: _getCurrentHostPlatformArchName(), + backendType: 'wayland', + logger: _logger ?? globals.logger, + processManager: _processManager ?? globals.processManager, + operatingSystemUtils: OperatingSystemUtils( + fileSystem: globals.fs, + logger: _logger ?? globals.logger, + platform: globals.platform, + processManager: const LocalProcessManager(), + )), + ]; + } + + @override + Future> getDiagnostics() async => const []; + + String _getCurrentHostPlatformArchName() { + final HostPlatform hostPlatform = getCurrentHostPlatform(); + return getNameForHostPlatformArch(hostPlatform); + } +} diff --git a/lib/elinux_doctor.dart b/lib/elinux_doctor.dart new file mode 100644 index 0000000..fb5cc1f --- /dev/null +++ b/lib/elinux_doctor.dart @@ -0,0 +1,126 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'dart:io'; + +import 'package:flutter_tools/src/android/android_workflow.dart'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/doctor.dart'; +import 'package:flutter_tools/src/doctor_validator.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/version.dart'; +import 'package:meta/meta.dart'; + +import 'executable.dart'; + +ELinuxWorkflow get eLinuxWorkflow => context.get(); +ELinuxValidator get eLinuxValidator => context.get(); + +/// See: [_DefaultDoctorValidatorsProvider] in `doctor.dart` +class ELinuxDoctorValidatorsProvider extends DoctorValidatorsProvider { + @override + List get validators { + final List validators = + DoctorValidatorsProvider.defaultInstance.validators; + return [ + validators.first, + eLinuxValidator, + ...validators.sublist(1) + ]; + } + + @override + List get workflows => [ + ...DoctorValidatorsProvider.defaultInstance.workflows, + eLinuxWorkflow, + ]; +} + +/// A validator that checks for eLinux SDK installation. +class ELinuxValidator extends DoctorValidator { + ELinuxValidator() : super('eLinux toolchain - develop for eLinux devices'); + + bool _validatePackages(List messages) { + return true; + } + + /// See: [AndroidValidator.validate] in `android_workflow.dart` + @override + Future validate() async { + final List messages = []; + + final FlutterVersion version = _FlutterELinuxVersion(); + messages.add(ValidationMessage(globals.userMessages.flutterRevision( + version.frameworkRevisionShort, + version.frameworkAge, + version.frameworkCommitDate, + ))); + messages.add(ValidationMessage( + globals.userMessages.engineRevision(version.engineRevisionShort))); + + if (!_validatePackages(messages)) { + return ValidationResult(ValidationType.partial, messages); + } + return ValidationResult(ValidationType.installed, messages); + } +} + +/// The eLinux-specific implementation of a [Workflow]. +/// +/// See: [AndroidWorkflow] in `android_workflow.dart` +class ELinuxWorkflow extends Workflow { + ELinuxWorkflow({ + @required OperatingSystemUtils operatingSystemUtils, + }) : _operatingSystemUtils = operatingSystemUtils; + + final OperatingSystemUtils _operatingSystemUtils; + + @override + bool get appliesToHostPlatform => + _operatingSystemUtils.hostPlatform != HostPlatform.linux_arm64; + + @override + bool get canLaunchDevices => true; + + @override + bool get canListDevices => true; + + @override + bool get canListEmulators => false; +} + +class _FlutterELinuxVersion extends FlutterVersion { + _FlutterELinuxVersion() : super(workingDirectory: rootPath); + + /// See: [Cache.getVersionFor] in `cache.dart` + String _getVersionFor(String artifactName) { + final File versionFile = globals.fs + .directory(rootPath) + .childDirectory('bin') + .childDirectory('internal') + .childFile('$artifactName.version'); + return versionFile.existsSync() + ? versionFile.readAsStringSync().trim() + : null; + } + + @override + String get engineRevision => _getVersionFor('engine'); + + /// See: [_runGit] in `version.dart` + String _runGit(String command) => globals.processUtils + .runSync(command.split(' '), workingDirectory: rootPath) + .stdout + .trim(); + + /// This should be overriden because [FlutterVersion._latestGitCommitDate] + /// runs the git log command in the `Cache.flutterRoot` directory. + @override + String get frameworkCommitDate => _runGit( + 'git -c log.showSignature=false log -n 1 --pretty=format:%ad --date=iso'); +} diff --git a/lib/elinux_package.dart b/lib/elinux_package.dart new file mode 100644 index 0000000..77fa10b --- /dev/null +++ b/lib/elinux_package.dart @@ -0,0 +1,103 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/cmake.dart'; +import 'package:flutter_tools/src/flutter_application_package.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/globals_null_migrated.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:meta/meta.dart'; + +import 'elinux_cmake_project.dart'; + +class ELinuxApplicationPackageFactory extends FlutterApplicationPackageFactory { + ELinuxApplicationPackageFactory() + : super( + androidSdk: globals.androidSdk, + processManager: globals.processManager, + logger: globals.logger, + userMessages: globals.userMessages, + fileSystem: globals.fs, + ); + + @override + Future getPackageForPlatform( + TargetPlatform platform, { + BuildInfo buildInfo, + File applicationBinary, + }) async { + if (platform == TargetPlatform.tester) { + return applicationBinary == null + ? ELinuxApp.fromELinuxProject(FlutterProject.current()) + : ELinuxApp.fromPrebuiltApp(applicationBinary); + } + return super.getPackageForPlatform(platform, + buildInfo: buildInfo, applicationBinary: applicationBinary); + } +} + +abstract class ELinuxApp extends ApplicationPackage { + ELinuxApp({@required String projectBundleId}) : super(id: projectBundleId); + + factory ELinuxApp.fromELinuxProject(FlutterProject project) { + return BuildableELinuxApp( + project: ELinuxProject.fromFlutter(project), + ); + } + + factory ELinuxApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { + return PrebuiltELinuxApp( + executable: applicationBinary.path, + ); + } + + @override + String get displayName => id; + + String executable(BuildMode buildMode, String targetArch); +} + +class PrebuiltELinuxApp extends ELinuxApp { + PrebuiltELinuxApp({ + @required String executable, + }) : _executable = executable, + super(projectBundleId: executable); + + final String _executable; + + @override + String executable(BuildMode buildMode, String targetArch) => _executable; + + @override + String get name => _executable; +} + +class BuildableELinuxApp extends ELinuxApp { + BuildableELinuxApp({@required this.project}) + : super(projectBundleId: project.parent.manifest.appName); + + final ELinuxProject project; + + @override + String executable(BuildMode buildMode, String targetArch) { + final String binaryName = getCmakeExecutableName(project); + return globals.fs.path.join( + 'build/elinux/', + targetArch, + getNameForBuildMode(buildMode), + 'bundle', + binaryName, + ); + } + + @override + String get name => project.parent.manifest.appName; +} diff --git a/lib/elinux_plugins.dart b/lib/elinux_plugins.dart new file mode 100644 index 0000000..05cbd7e --- /dev/null +++ b/lib/elinux_plugins.dart @@ -0,0 +1,462 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/terminal.dart'; +import 'package:flutter_tools/src/build_system/targets/web.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/dart/language_version.dart'; +import 'package:flutter_tools/src/dart/package_map.dart'; +import 'package:flutter_tools/src/flutter_plugins.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/platform_plugins.dart'; +import 'package:flutter_tools/src/plugins.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; + +import 'package:meta/meta.dart'; +import 'package:package_config/package_config.dart'; +import 'package:yaml/yaml.dart'; + +import 'elinux_cmake_project.dart'; + +/// Contains the parameters to template a elinux plugin. +/// +/// The [name] of the plugin is required. Either [dartPluginClass] or +/// [pluginClass] are required. [pluginClass] will be the entry point to the +/// plugin's native code. If [pluginClass] is not empty, the [fileName] +/// containing the plugin's code is required. +/// +/// Source: [LinuxPlugin] in `platform_plugins.dart` +class ELinuxPlugin extends PluginPlatform implements NativeOrDartPlugin { + ELinuxPlugin({ + @required this.name, + @required this.directory, + this.pluginClass, + this.dartPluginClass, + this.fileName, + }) : assert(pluginClass != null || dartPluginClass != null); + + factory ELinuxPlugin.fromYaml( + String name, Directory directory, YamlMap yaml) { + assert(validate(yaml)); + return ELinuxPlugin( + name: name, + directory: directory, + pluginClass: yaml[kPluginClass] as String, + dartPluginClass: yaml[kDartPluginClass] as String, + fileName: yaml['fileName'] as String, + ); + } + + static bool validate(YamlMap yaml) { + if (yaml == null) { + return false; + } + return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String; + } + + static const String kConfigKey = 'elinux'; + + final String name; + final Directory directory; + final String pluginClass; + final String dartPluginClass; + final String fileName; + + @override + bool isNative() => pluginClass != null; + + @override + Map toMap() { + return { + 'name': name, + if (pluginClass != null) 'class': pluginClass, + if (dartPluginClass != null) 'dartPluginClass': dartPluginClass, + 'file': fileName, + }; + } + + File get projectFile => directory.childFile('project_def.prop'); + + final RegExp _propertyFormat = RegExp(r'(\S+)\s*\+?=(.*)'); + + Map _properties; + + String getProperty(String key) { + if (_properties == null) { + if (!projectFile.existsSync()) { + return null; + } + _properties = {}; + + for (final String line in projectFile.readAsLinesSync()) { + final Match match = _propertyFormat.firstMatch(line); + if (match == null) { + continue; + } + final String key = match.group(1); + final String value = match.group(2).trim(); + _properties[key] = value; + } + } + return _properties.containsKey(key) ? _properties[key] : null; + } + + List getPropertyAsAbsolutePaths(String key) { + final String property = getProperty(key); + if (property == null) { + return []; + } + + final List paths = []; + for (final String element in property.split(' ')) { + if (globals.fs.path.isAbsolute(element)) { + paths.add(element); + } else { + paths.add(globals.fs.path + .normalize(globals.fs.path.join(directory.path, element))); + } + } + return paths; + } +} + +/// Any [FlutterCommand] that invokes [usesPubOption] or [targetFile] should +/// depend on this mixin to ensure plugins are correctly configured for eLinux. +/// +/// See: [FlutterCommand.verifyThenRunCommand] in `flutter_command.dart` +mixin ELinuxExtension on FlutterCommand { + String _entrypoint; + + bool get _usesTargetOption => argParser.options.containsKey('target'); + + @override + Future verifyThenRunCommand(String commandPath) async { + if (super.shouldRunPub) { + // TODO(swift-kim): Should run pub get first before injecting plugins. + await ensureReadyForELinuxTooling(FlutterProject.current()); + } + if (_usesTargetOption) { + _entrypoint = + await _createEntrypoint(FlutterProject.current(), super.targetFile); + } + return super.verifyThenRunCommand(commandPath); + } + + @override + String get targetFile => _entrypoint ?? super.targetFile; +} + +/// Creates an entrypoint wrapper of [targetFile] and returns its path. +/// This effectively adds support for Dart plugins. +/// +/// Source: [WebEntrypointTarget.build] in `web.dart` +Future _createEntrypoint( + FlutterProject project, String targetFile) async { + final List dartPlugins = + await findELinuxPlugins(project, dartOnly: true); + if (dartPlugins.isEmpty) { + return targetFile; + } + + final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project); + if (!eLinuxProject.existsSync()) { + return targetFile; + } + + final File entrypoint = eLinuxProject.managedDirectory.childFile('main.dart') + ..createSync(recursive: true); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + project.directory.childFile('.packages'), + logger: globals.logger, + ); + final FlutterProject flutterProject = FlutterProject.current(); + final LanguageVersion languageVersion = determineLanguageVersion( + globals.fs.file(targetFile), + packageConfig[flutterProject.manifest.appName], + Cache.flutterRoot, + ); + + final Uri mainUri = globals.fs.file(targetFile).absolute.uri; + final String mainImport = + packageConfig.toPackageUri(mainUri)?.toString() ?? mainUri.toString(); + + entrypoint.writeAsStringSync(''' +// +// Generated file. Do not edit. +// +// @dart=${languageVersion.major}.${languageVersion.minor} + +import '$mainImport' as entrypoint; +import 'generated_plugin_registrant.dart'; + +Future main() async { + registerPlugins(); + entrypoint.main(); +} +'''); + return entrypoint.path; +} + +const List _knownPlugins = [ + 'audioplayers', + 'battery', + 'battery_plus', + 'camera', + 'connectivity', + 'device_info', + 'flutter_tts', + 'image_picker', + 'integration_test', + 'package_info', + 'package_info_plus', + 'path_provider', + 'permission_handler', + 'sensors', + 'sensors_plus', + 'share', + 'shared_preferences', + 'url_launcher', + 'video_player', + 'webview_flutter', + 'wifi_info_flutter', +]; + +/// This function must be called whenever [FlutterProject.regeneratePlatformSpecificTooling] +/// or [FlutterProject.ensureReadyForPlatformSpecificTooling] is called. +/// +/// See: [FlutterProject.ensureReadyForPlatformSpecificTooling] in `project.dart` +Future ensureReadyForELinuxTooling(FlutterProject project) async { + if (!project.directory.existsSync() || + project.hasExampleApp || + project.isPlugin) { + return; + } + final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project); + await eLinuxProject.ensureReadyForPlatformSpecificTooling(); + + await injectELinuxPlugins(project); +} + +/// See: [injectPlugins] in `plugins.dart` +Future injectELinuxPlugins(FlutterProject project) async { + final ELinuxProject eLinuxProject = ELinuxProject.fromFlutter(project); + if (eLinuxProject.existsSync()) { + final List dartPlugins = + await findELinuxPlugins(project, dartOnly: true); + final List nativePlugins = + await findELinuxPlugins(project, nativeOnly: true); + _writeDartPluginRegistrant(eLinuxProject.managedDirectory, dartPlugins); + _writePluginCmakefileTemplate( + eLinuxProject.managedDirectory, nativePlugins); + } + + final List plugins = + (await findPlugins(project)).map((Plugin p) => p.name).toList(); + for (final String plugin in plugins) { + final String eLinuxPlugin = '${plugin}_elinux'; + if (_knownPlugins.contains(plugin) && !plugins.contains(eLinuxPlugin)) { + globals.printStatus( + '$eLinuxPlugin is available on pub.dev. Did you forget to add to pubspec.yaml?', + color: TerminalColor.yellow, + ); + } + } +} + +/// Source: [findPlugins] in `plugins.dart` +Future> findELinuxPlugins( + FlutterProject project, { + bool dartOnly = false, + bool nativeOnly = false, + bool throwOnError = true, +}) async { + final List plugins = []; + final File packagesFile = project.directory.childFile('.packages'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + packagesFile, + logger: globals.logger, + throwOnError: throwOnError, + ); + for (final Package package in packageConfig.packages) { + final Uri packageRoot = package.packageUriRoot.resolve('..'); + final ELinuxPlugin plugin = _pluginFromPackage(package.name, packageRoot); + if (plugin == null) { + continue; + } else if (nativeOnly && plugin.pluginClass == null) { + continue; + } else if (dartOnly && plugin.dartPluginClass == null) { + continue; + } + plugins.add(plugin); + } + return plugins; +} + +/// Source: [_pluginFromPackage] in `plugins.dart` +ELinuxPlugin _pluginFromPackage(String name, Uri packageRoot) { + final String pubspecPath = + globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml')); + if (!globals.fs.isFileSync(pubspecPath)) { + return null; + } + + dynamic pubspec; + try { + pubspec = loadYaml(globals.fs.file(pubspecPath).readAsStringSync()); + } on YamlException catch (err) { + globals.printTrace('Failed to parse plugin manifest for $name: $err'); + } + if (pubspec == null) { + return null; + } + final dynamic flutterConfig = pubspec['flutter']; + if (flutterConfig == null || !(flutterConfig.containsKey('plugin') as bool)) { + return null; + } + + final Directory packageDir = globals.fs.directory(packageRoot); + globals.printTrace('Found plugin $name at ${packageDir.path}'); + + final YamlMap pluginYaml = flutterConfig['plugin'] as YamlMap; + if (pluginYaml == null || pluginYaml['platforms'] == null) { + return null; + } + final YamlMap platformsYaml = pluginYaml['platforms'] as YamlMap; + if (platformsYaml == null || platformsYaml[ELinuxPlugin.kConfigKey] == null) { + return null; + } + return ELinuxPlugin.fromYaml( + name, + packageDir.childDirectory('elinux'), + platformsYaml[ELinuxPlugin.kConfigKey] as YamlMap, + ); +} + +/// See: [_writeWebPluginRegistrant] in `plugins.dart` +void _writeDartPluginRegistrant( + Directory registryDirectory, + List plugins, +) { + final List> pluginConfigs = + plugins.map((ELinuxPlugin plugin) => plugin.toMap()).toList(); + final Map context = { + 'plugins': pluginConfigs, + }; + _renderTemplateToFile( + ''' +// +// Generated file. Do not edit. +// + +// ignore_for_file: lines_longer_than_80_chars + +{{#plugins}} +import 'package:{{name}}/{{name}}.dart'; +{{/plugins}} + +// ignore: public_member_api_docs +void registerPlugins() { +{{#plugins}} + {{dartPluginClass}}.register(); +{{/plugins}} +} +''', + context, + registryDirectory.childFile('generated_plugin_registrant.dart').path, + ); +} + +/// See: [_writeWindowsPluginFiles] in `plugins.dart` +void _writePluginCmakefileTemplate( + Directory registryDirectory, + List plugins, +) { + final List> pluginConfigs = + plugins.map((ELinuxPlugin plugin) => plugin.toMap()).toList(); + final Map context = { + 'plugins': pluginConfigs, + }; + _renderTemplateToFile( + ''' +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ +''', + context, + registryDirectory.childFile('generated_plugin_registrant.h').path, + ); + _renderTemplateToFile( + ''' +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} +''', + context, + registryDirectory.childFile('generated_plugin_registrant.cc').path, + ); + _renderTemplateToFile( + r''' +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +{{#plugins}} + {{name}} +{{/plugins}} +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory( + ${USER_PROJECT_PATH}/flutter/plugins/${plugin}/elinux plugins/${plugin}) + + target_link_libraries(${TARGET} + PRIVATE + ${plugin}_plugin + ) + + list(APPEND PLUGIN_BUNDLED_LIBRARIES + ${PROJECT_BINARY_DIR}/plugins/${plugin}/lib${plugin}_plugin.so + ) +endforeach(plugin) +''', + context, + registryDirectory.childFile('generated_plugins.cmake').path, + ); +} + +/// Source: [_renderTemplateToFile] in `plugins.dart` (exact copy) +void _renderTemplateToFile(String template, dynamic context, String filePath) { + final String renderedTemplate = globals.templateRenderer + .renderString(template, context, htmlEscapeValues: false); + final File file = globals.fs.file(filePath); + file.createSync(recursive: true); + file.writeAsStringSync(renderedTemplate); +} diff --git a/lib/executable.dart b/lib/executable.dart new file mode 100644 index 0000000..258442f --- /dev/null +++ b/lib/executable.dart @@ -0,0 +1,144 @@ +// Copyright 2021 Sony Group Corporation. All rights reserved. +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'dart:io'; + +import 'package:flutter_tools/executable.dart' as flutter; +import 'package:flutter_tools/runner.dart' as runner; +import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/base/context.dart'; +import 'package:flutter_tools/src/base/template.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/config.dart'; +import 'package:flutter_tools/src/commands/devices.dart'; +import 'package:flutter_tools/src/commands/doctor.dart'; +import 'package:flutter_tools/src/commands/emulators.dart'; +import 'package:flutter_tools/src/commands/format.dart'; +import 'package:flutter_tools/src/commands/generate_localizations.dart'; +import 'package:flutter_tools/src/commands/install.dart'; +import 'package:flutter_tools/src/commands/logs.dart'; +import 'package:flutter_tools/src/commands/screenshot.dart'; +import 'package:flutter_tools/src/commands/symbolize.dart'; +import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/doctor.dart'; +import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/isolated/mustache_template.dart'; +import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:path/path.dart'; + +import 'commands/analyze.dart'; +import 'commands/attach.dart'; +import 'commands/build.dart'; +import 'commands/clean.dart'; +import 'commands/create.dart'; +import 'commands/drive.dart'; +import 'commands/packages.dart'; +import 'commands/precache.dart'; +import 'commands/run.dart'; +import 'commands/test.dart'; +import 'elinux_cache.dart'; +import 'elinux_device_discovery.dart'; +import 'elinux_doctor.dart'; +import 'elinux_package.dart'; + +/// Main entry point for commands. +/// +/// Source: [flutter.main] in `executable.dart` (some commands and options were omitted) +Future main(List args) async { + final bool veryVerbose = args.contains('-vv'); + final bool verbose = + args.contains('-v') || args.contains('--verbose') || veryVerbose; + + final bool doctor = (args.isNotEmpty && args.first == 'doctor') || + (args.length == 2 && verbose && args.last == 'doctor'); + final bool help = args.contains('-h') || + args.contains('--help') || + (args.isNotEmpty && args.first == 'help') || + (args.length == 1 && verbose); + final bool muteCommandLogging = (help || doctor) && !veryVerbose; + final bool verboseHelp = help && verbose; + + final bool hasSpecifiedDeviceId = + args.contains('-d') || args.contains('--device-id'); + + args = [ + '--suppress-analytics', // Suppress flutter analytics by default. + '--no-version-check', + if (!hasSpecifiedDeviceId) ...['--device-id', 'elinux'], + ...args, + ]; + + Cache.flutterRoot = join(rootPath, 'flutter'); + + await runner.run( + args, + () => [ + // Commands directly from flutter_tools. + ConfigCommand(verboseHelp: verboseHelp), + DevicesCommand(verboseHelp: verboseHelp), + DoctorCommand(verbose: verbose), + EmulatorsCommand(), + FormatCommand(), + GenerateLocalizationsCommand( + fileSystem: globals.fs, + logger: globals.logger, + ), + InstallCommand(), + LogsCommand(), + ScreenshotCommand(), + SymbolizeCommand(stdio: globals.stdio, fileSystem: globals.fs), + // Commands extended for ELinux. + ELinuxAnalyzeCommand(verboseHelp: verboseHelp), + ELinuxAttachCommand(verboseHelp: verboseHelp), + ELinuxBuildCommand(verboseHelp: verboseHelp), + ELinuxCleanCommand(verbose: verbose), + ELinuxCreateCommand(verboseHelp: verboseHelp), + ELinuxDriveCommand(verboseHelp: verboseHelp), + ELinuxPackagesCommand(), + ELinuxPrecacheCommand( + verboseHelp: verboseHelp, + cache: globals.cache, + logger: globals.logger, + platform: globals.platform, + featureFlags: featureFlags, + ), + ELinuxRunCommand(verboseHelp: verboseHelp), + ELinuxTestCommand(verboseHelp: verboseHelp), + ], + verbose: verbose, + verboseHelp: verboseHelp, + muteCommandLogging: muteCommandLogging, + reportCrashes: false, + overrides: { + Cache: () => ELinuxFlutterCache( + fileSystem: globals.fs, + logger: globals.logger, + platform: globals.platform, + osUtils: globals.os, + ), + TemplateRenderer: () => const MustacheTemplateRenderer(), + ApplicationPackageFactory: () => ELinuxApplicationPackageFactory(), + DeviceManager: () => ELinuxDeviceManager(), + DoctorValidatorsProvider: () => ELinuxDoctorValidatorsProvider(), + ELinuxWorkflow: () => ELinuxWorkflow( + operatingSystemUtils: globals.os, + ), + ELinuxValidator: () => ELinuxValidator(), + }, + ); +} + +/// See: [Cache.defaultFlutterRoot] in `cache.dart` +String get rootPath { + final String scriptPath = Platform.script.toFilePath(); + return normalize(join( + scriptPath, + scriptPath.endsWith('.snapshot') ? '../../..' : '../..', + )); +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..b7a4d2b --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,26 @@ +name: flutter_elinux +description: Flutter application build tools for Embedded Linux devices +homepage: https://github.com/sony/flutter-embedded-linux +version: 2.2.1 +publish_to: none + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter_tools: + path: flutter/packages/flutter_tools + file: # inherit from flutter_tools + meta: + package_config: + path: + process: + xml: + yaml: + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: + pedantic: + test: diff --git a/templates/app/CMakeLists.txt.tmpl b/templates/app/CMakeLists.txt.tmpl new file mode 100644 index 0000000..c031dcb --- /dev/null +++ b/templates/app/CMakeLists.txt.tmpl @@ -0,0 +1,104 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "{{projectName}}") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Configure build option to target backend. +if (NOT FLUTTER_TARGET_BACKEND_TYPE) + set(FLUTTER_TARGET_BACKEND_TYPE "wayland" CACHE + STRING "Flutter target backend type" FORCE) + set_property(CACHE FLUTTER_TARGET_BACKEND_TYPE PROPERTY STRINGS + "wayland" "gbm" "eglstream" "x11") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +install(FILES "${FLUTTER_EMBEDDER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/templates/app/flutter/CMakeLists.txt b/templates/app/flutter/CMakeLists.txt new file mode 100644 index 0000000..376be86 --- /dev/null +++ b/templates/app/flutter/CMakeLists.txt @@ -0,0 +1,108 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_engine.so") +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_gbm.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_eglstream.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_x11.so") +else() + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_wayland.so") +endif() + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_EMBEDDER_LIBRARY ${FLUTTER_EMBEDDER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/elinux/" PARENT_SCOPE) +set(AOT_LIBRARY "${EPHEMERAL_DIR}/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_plugin_registrar.h" + "flutter_messenger.h" + "flutter_texture_registrar.h" + "flutter_elinux.h" + "flutter_platform_views.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE "${FLUTTER_EMBEDDER_LIBRARY}") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${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" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + "${FLUTTER_EMBEDDER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/templates/app/runner/CMakeLists.txt b/templates/app/runner/CMakeLists.txt new file mode 100644 index 0000000..8bb1779 --- /dev/null +++ b/templates/app/runner/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + add_definitions(-DFLUTTER_TARGET_BACKEND_GBM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + add_definitions(-DFLUTTER_TARGET_BACKEND_EGLSTREAM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + add_definitions(-DFLUTTER_TARGET_BACKEND_X11) +else() + add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND) +endif() + +add_executable(${BINARY_NAME} + "flutter_window.cc" + "main.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/templates/app/runner/command_options.h b/templates/app/runner/command_options.h new file mode 100644 index 0000000..977cdc8 --- /dev/null +++ b/templates/app/runner/command_options.h @@ -0,0 +1,367 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMMAND_OPTIONS_ +#define COMMAND_OPTIONS_ + +#include +#include +#include +#include +#include +#include +#include + +// todo: Supports other types besides int, string. + +namespace commandline { + +namespace { +constexpr char kOptionStyleNormal[] = "--"; +constexpr char kOptionStyleShort[] = "-"; +constexpr char kOptionValueForHelpMessage[] = "="; +} // namespace + +class Exception : public std::exception { + public: + Exception(const std::string& msg) : msg_(msg) {} + ~Exception() throw() {} + + const char* what() const throw() { return msg_.c_str(); } + + private: + std::string msg_; +}; + +class CommandOptions { + public: + CommandOptions() = default; + ~CommandOptions() = default; + + void AddWithoutValue(const std::string& name, const std::string& short_name, + const std::string& description, bool required) { + Add(name, short_name, description, "", + ReaderString(), required, false); + } + + void AddInt(const std::string& name, const std::string& short_name, + const std::string& description, const int& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderInt(), required, true); + } + + void AddString(const std::string& name, const std::string& short_name, + const std::string& description, + const std::string& default_value, bool required) { + Add(name, short_name, description, default_value, + ReaderString(), required, true); + } + + template + void Add(const std::string& name, const std::string& short_name, + const std::string& description, const T default_value, + F reader = F(), bool required = true, bool required_value = true) { + if (options_.find(name) != options_.end()) { + std::cerr << "Already registered option: " << name << std::endl; + return; + } + + if (lut_short_options_.find(short_name) != lut_short_options_.end()) { + std::cerr << short_name << "is already registered" << std::endl; + return; + } + lut_short_options_[short_name] = name; + + options_[name] = std::make_unique>( + name, short_name, description, default_value, reader, required, + required_value); + + // register to show help message. + registration_order_options_.push_back(options_[name].get()); + } + + bool Exist(const std::string& name) { + auto itr = options_.find(name); + return itr != options_.end() && itr->second->HasValue(); + } + + template + const T& GetValue(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + throw Exception("Not found: " + name); + } + + auto* option_value = dynamic_cast*>(itr->second.get()); + if (!option_value) { + throw Exception("Type mismatch: " + name); + } + return option_value->GetValue(); + } + + bool Parse(int argc, const char* const* argv) { + if (argc < 1) { + errors_.push_back("No options"); + return false; + } + + command_name_ = argv[0]; + for (auto i = 1; i < argc; i++) { + const std::string arg(argv[i]); + + // normal options: e.g. --bundle=/data/sample/bundle --fullscreen + if (arg.length() > 2 && + arg.substr(0, 2).compare(kOptionStyleNormal) == 0) { + const size_t option_value_len = arg.find("=") != std::string::npos + ? (arg.length() - arg.find("=")) + : 0; + const bool has_value = option_value_len != 0; + std::string option_name = + arg.substr(2, arg.length() - 2 - option_value_len); + + if (options_.find(option_name) == options_.end()) { + errors_.push_back("Not found option: " + option_name); + continue; + } + + if (!has_value && options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " requres an option value"); + continue; + } + + if (has_value && !options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " doesn't requres an option value"); + continue; + } + + if (has_value) { + SetOptionValue(option_name, arg.substr(arg.find("=") + 1)); + } else { + SetOption(option_name); + } + } + // short options: e.g. -f /foo/file.txt -h 640 -abc + else if (arg.length() > 1 && + arg.substr(0, 1).compare(kOptionStyleShort) == 0) { + for (size_t j = 1; j < arg.length(); j++) { + const std::string option_name{argv[i][j]}; + + if (lut_short_options_.find(option_name) == + lut_short_options_.end()) { + errors_.push_back("Not found short option: " + option_name); + break; + } + + if (j == arg.length() - 1 && + options_[lut_short_options_[option_name]]->IsRequiredValue()) { + if (i == argc - 1) { + errors_.push_back("Invalid format option: " + option_name); + break; + } + SetOptionValue(lut_short_options_[option_name], argv[++i]); + } else { + SetOption(lut_short_options_[option_name]); + } + } + } else { + errors_.push_back("Invalid format option: " + arg); + } + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired() && + !registration_order_options_[i]->HasValue()) { + errors_.push_back( + std::string(registration_order_options_[i]->GetName()) + + " option is mandatory."); + } + } + + return errors_.size() == 0; + } + + std::string GetError() { return errors_.size() > 0 ? errors_[0] : ""; } + + std::vector& GetErrors() { return errors_; } + + std::string ShowHelp() { + std::ostringstream ostream; + + ostream << "Usage: " << command_name_ << " "; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired()) { + ostream << registration_order_options_[i]->GetHelpShortMessage() << " "; + } + } + ostream << std::endl; + + ostream << "Global options:" << std::endl; + size_t max_name_len = 0; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + max_name_len = std::max( + max_name_len, registration_order_options_[i]->GetName().length()); + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (!registration_order_options_[i]->GetShortName().empty()) { + ostream << kOptionStyleShort + << registration_order_options_[i]->GetShortName() << ", "; + } else { + ostream << std::string(4, ' '); + } + + size_t index_adjust = 0; + constexpr int kSpacerNum = 5; + auto need_value = registration_order_options_[i]->IsRequiredValue(); + ostream << kOptionStyleNormal + << registration_order_options_[i]->GetName(); + if (need_value) { + ostream << kOptionValueForHelpMessage; + index_adjust += std::string(kOptionValueForHelpMessage).length(); + } + ostream << std::string( + max_name_len + kSpacerNum - index_adjust - + registration_order_options_[i]->GetName().length(), + ' '); + ostream << registration_order_options_[i]->GetDescription() << std::endl; + } + + return ostream.str(); + } + + private: + struct ReaderInt { + int operator()(const std::string& value) { return std::stoi(value); } + }; + + struct ReaderString { + std::string operator()(const std::string& value) { return value; } + }; + + class Option { + public: + Option(const std::string& name, const std::string& short_name, + const std::string& description, bool required, bool required_value) + : name_(name), + short_name_(short_name), + description_(description), + is_required_(required), + is_required_value_(required_value), + value_set_(false){}; + virtual ~Option() = default; + + const std::string& GetName() const { return name_; }; + + const std::string& GetShortName() const { return short_name_; }; + + const std::string& GetDescription() const { return description_; }; + + const std::string GetHelpShortMessage() const { + std::string message = kOptionStyleNormal + name_; + if (is_required_value_) { + message += kOptionValueForHelpMessage; + } + return message; + } + + bool IsRequired() const { return is_required_; }; + + bool IsRequiredValue() const { return is_required_value_; }; + + void Set() { value_set_ = true; }; + + virtual bool SetValue(const std::string& value) = 0; + + virtual bool HasValue() const = 0; + + protected: + std::string name_; + std::string short_name_; + std::string description_; + bool is_required_; + bool is_required_value_; + bool value_set_; + }; + + template + class OptionValue : public Option { + public: + OptionValue(const std::string& name, const std::string& short_name, + const std::string& description, const T& default_value, + bool required, bool required_value) + : Option(name, short_name, description, required, required_value), + default_value_(default_value), + value_(default_value){}; + virtual ~OptionValue() = default; + + bool SetValue(const std::string& value) { + value_ = Read(value); + value_set_ = true; + return true; + } + + bool HasValue() const { return value_set_; } + + const T& GetValue() const { return value_; } + + protected: + virtual T Read(const std::string& s) = 0; + + T default_value_; + T value_; + }; + + template + class OptionValueReader : public OptionValue { + public: + OptionValueReader(const std::string& name, const std::string& short_name, + const std::string& description, const T default_value, + F reader, bool required, bool required_value) + : OptionValue(name, short_name, description, default_value, required, + required_value), + reader_(reader) {} + ~OptionValueReader() = default; + + private: + T Read(const std::string& value) { return reader_(value); } + + F reader_; + }; + + bool SetOption(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + itr->second->Set(); + return true; + } + + bool SetOptionValue(const std::string& name, const std::string& value) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + if (!itr->second->SetValue(value)) { + errors_.push_back("Invalid option value: " + name + " = " + value); + return false; + } + return true; + } + + std::string command_name_; + std::unordered_map> options_; + std::unordered_map lut_short_options_; + std::vector registration_order_options_; + std::vector errors_; +}; + +} // namespace commandline + +#endif // COMMAND_OPTIONS_ diff --git a/templates/app/runner/flutter_embedder_options.h b/templates/app/runner/flutter_embedder_options.h new file mode 100644 index 0000000..51e378f --- /dev/null +++ b/templates/app/runner/flutter_embedder_options.h @@ -0,0 +1,103 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_EMBEDDER_OPTIONS_ +#define FLUTTER_EMBEDDER_OPTIONS_ + +#include + +#include + +#include "command_options.h" + +class FlutterEmbedderOptions { + public: + FlutterEmbedderOptions() { + options_.AddString("bundle", "b", "Path to Flutter app bundle", "./bundle", + true); + options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer", + false); +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + // no more options. +#elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Flutter app window width", 1280, false); + options_.AddInt("height", "h", "Flutter app window height", 720, false); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddWithoutValue("onscreen-keyboard", "k", + "Enable on-screen keyboard", false); + options_.AddWithoutValue("window-decoration", "d", + "Enable window decorations", false); + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Flutter app window width", 1280, false); + options_.AddInt("height", "h", "Flutter app window height", 720, false); +#endif + } + ~FlutterEmbedderOptions() = default; + + bool Parse(int argc, char** argv) { + if (!options_.Parse(argc, argv)) { + std::cerr << options_.GetError() << std::endl; + std::cout << options_.ShowHelp(); + return false; + } + + bundle_path_ = options_.GetValue("bundle"); + use_mouse_cursor_ = !options_.Exist("no-cursor"); + +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_view_mode_ = flutter::FlutterViewController::ViewMode::kFullscreen; +#elif defined(FLUTTER_TARGET_BACKEND_X11) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); + use_window_decoration_ = options_.Exist("window-decoration"); + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#endif + + return true; + } + + std::string BundlePath() const { return bundle_path_; } + bool IsUseMouseCursor() const { return use_mouse_cursor_; } + bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; } + bool IsUseWindowDecoraation() const { return use_window_decoration_; } + flutter::FlutterViewController::ViewMode WindowViewMode() const { + return window_view_mode_; + } + int WindowWidth() const { return window_width_; } + int WindowHeight() const { return window_height_; } + + private: + commandline::CommandOptions options_; + + std::string bundle_path_; + bool use_mouse_cursor_ = true; + bool use_onscreen_keyboard_ = false; + bool use_window_decoration_ = false; + flutter::FlutterViewController::ViewMode window_view_mode_ = + flutter::FlutterViewController::ViewMode::kNormal; + int window_width_ = 1280; + int window_height_ = 720; +}; + +#endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/templates/app/runner/flutter_window.cc b/templates/app/runner/flutter_window.cc new file mode 100644 index 0000000..0c5b639 --- /dev/null +++ b/templates/app/runner/flutter_window.cc @@ -0,0 +1,79 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_window.h" + +#include +#include +#include +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project) + : view_properties_(view_properties), project_(project) {} + +bool FlutterWindow::OnCreate() { + flutter_view_controller_ = std::make_unique( + view_properties_, project_); + + // Ensure that basic setup of the controller was successful. + if (!flutter_view_controller_->engine() || + !flutter_view_controller_->view()) { + return false; + } + + // Register Flutter plugins. + RegisterPlugins(flutter_view_controller_->engine()); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_view_controller_) { + flutter_view_controller_ = nullptr; + } +} + +void FlutterWindow::Run() { + // Main loop. + auto next_flutter_event_time = + std::chrono::steady_clock::time_point::clock::now(); + while (flutter_view_controller_->view()->DispatchEvent()) { + // Wait until the next event. + { + auto wait_duration = + std::max(std::chrono::nanoseconds(0), + next_flutter_event_time - + std::chrono::steady_clock::time_point::clock::now()); + std::this_thread::sleep_for( + std::chrono::duration_cast(wait_duration)); + } + + // Processes any pending events in the Flutter engine, and returns the + // number of nanoseconds until the next scheduled event (or max, if none). + auto wait_duration = flutter_view_controller_->engine()->ProcessMessages(); + { + auto next_event_time = std::chrono::steady_clock::time_point::max(); + if (wait_duration != std::chrono::nanoseconds::max()) { + next_event_time = + std::min(next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + wait_duration); + } else { + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); + } + next_flutter_event_time = + std::max(next_flutter_event_time, next_event_time); + } + } +} diff --git a/templates/app/runner/flutter_window.h b/templates/app/runner/flutter_window.h new file mode 100644 index 0000000..20b9cb8 --- /dev/null +++ b/templates/app/runner/flutter_window.h @@ -0,0 +1,34 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_WINDOW_ +#define FLUTTER_WINDOW_ + +#include +#include + +#include + +class FlutterWindow { + public: + explicit FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project); + ~FlutterWindow() = default; + + // Prevent copying. + FlutterWindow(FlutterWindow const&) = delete; + FlutterWindow& operator=(FlutterWindow const&) = delete; + + bool OnCreate(); + void OnDestroy(); + void Run(); + + private: + flutter::FlutterViewController::ViewProperties view_properties_; + flutter::DartProject project_; + std::unique_ptr flutter_view_controller_; +}; + +#endif // FLUTTER_WINDOW_ diff --git a/templates/app/runner/main.cc b/templates/app/runner/main.cc new file mode 100644 index 0000000..9c9c91d --- /dev/null +++ b/templates/app/runner/main.cc @@ -0,0 +1,46 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include +#include + +#include "flutter_embedder_options.h" +#include "flutter_window.h" + +int main(int argc, char** argv) { + FlutterEmbedderOptions options; + if (!options.Parse(argc, argv)) { + return 0; + } + + // Creates the Flutter project. + const auto bundle_path = options.BundlePath(); + const std::wstring fl_path(bundle_path.begin(), bundle_path.end()); + flutter::DartProject project(fl_path); + auto command_line_arguments = std::vector(); + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + flutter::FlutterViewController::ViewProperties view_properties = {}; + view_properties.width = options.WindowWidth(); + view_properties.height = options.WindowHeight(); + view_properties.view_mode = options.WindowViewMode(); + view_properties.use_mouse_cursor = options.IsUseMouseCursor(); + view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); + view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + + // The Flutter instance hosted by this window. + FlutterWindow window(view_properties, project); + if (!window.OnCreate()) { + std::cerr << "Failed to create a Flutter window." << std::endl; + return 0; + } + window.Run(); + window.OnDestroy(); + + return 0; +} diff --git a/templates/template_manifest.json b/templates/template_manifest.json new file mode 100644 index 0000000..5306a63 --- /dev/null +++ b/templates/template_manifest.json @@ -0,0 +1,293 @@ +{ + "version": 1.0, + "_comment": "A listing of all possible template output files.", + "files": [ + "templates/app/elinux.tmpl/.gitignore", + "templates/app/elinux.tmpl/CMakeLists.txt.tmpl", + "templates/app/elinux.tmpl/flutter/CMakeLists.txt", + "templates/app/elinux.tmpl/runner/CMakeLists.txt", + "templates/app/elinux.tmpl/runner/command_options.h", + "templates/app/elinux.tmpl/runner/flutter_embedder_options.h", + "templates/app/elinux.tmpl/runner/flutter_window.cc", + "templates/app/elinux.tmpl/runner/flutter_window.h", + "templates/app/elinux.tmpl/runner/main.cc", + + "templates/app/.gitignore.tmpl", + "templates/app/.idea/libraries/Dart_SDK.xml.tmpl", + "templates/app/.idea/libraries/KotlinJavaRuntime.xml.tmpl", + "templates/app/.idea/modules.xml.tmpl", + "templates/app/.idea/runConfigurations/main_dart.xml.tmpl", + "templates/app/.idea/workspace.xml.tmpl", + "templates/app/.metadata.tmpl", + "templates/app/android-java.tmpl/app/build.gradle.tmpl", + "templates/app/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl", + "templates/app/android-java.tmpl/build.gradle", + "templates/app/android-java.tmpl/projectName_android.iml.tmpl", + "templates/app/android-kotlin.tmpl/app/build.gradle.tmpl", + "templates/app/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl", + "templates/app/android-kotlin.tmpl/build.gradle", + "templates/app/android-kotlin.tmpl/projectName_android.iml.tmpl", + "templates/app/android.tmpl/.gitignore", + "templates/app/android.tmpl/app/src/debug/AndroidManifest.xml.tmpl", + "templates/app/android.tmpl/app/src/main/AndroidManifest.xml.tmpl", + "templates/app/android.tmpl/app/src/main/res/drawable/launch_background.xml", + "templates/app/android.tmpl/app/src/main/res/drawable-v21/launch_background.xml", + "templates/app/android.tmpl/app/src/main/res/mipmap-hdpi/ic_launcher.png", + "templates/app/android.tmpl/app/src/main/res/mipmap-mdpi/ic_launcher.png", + "templates/app/android.tmpl/app/src/main/res/mipmap-xhdpi/ic_launcher.png", + "templates/app/android.tmpl/app/src/main/res/mipmap-xxhdpi/ic_launcher.png", + "templates/app/android.tmpl/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png", + "templates/app/android.tmpl/app/src/main/res/values/styles.xml", + "templates/app/android.tmpl/app/src/main/res/values-night/styles.xml", + "templates/app/android.tmpl/app/src/profile/AndroidManifest.xml.tmpl", + "templates/app/android.tmpl/gradle/wrapper/gradle-wrapper.properties", + "templates/app/android.tmpl/gradle.properties.tmpl", + "templates/app/android.tmpl/settings.gradle", + "templates/app/ios-objc.tmpl/Runner/AppDelegate.h", + "templates/app/ios-objc.tmpl/Runner/AppDelegate.m", + "templates/app/ios-objc.tmpl/Runner/main.m", + "templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl", + "templates/app/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme", + "templates/app/ios-swift.tmpl/Runner/AppDelegate.swift", + "templates/app/ios-swift.tmpl/Runner/Runner-Bridging-Header.h", + "templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl", + "templates/app/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme", + "templates/app/ios.tmpl/.gitignore", + "templates/app/ios.tmpl/Flutter/AppFrameworkInfo.plist", + "templates/app/ios.tmpl/Flutter/Debug.xcconfig", + "templates/app/ios.tmpl/Flutter/Release.xcconfig", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json", + "templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png", + "templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/README.md", + "templates/app/ios.tmpl/Runner/Base.lproj/LaunchScreen.storyboard", + "templates/app/ios.tmpl/Runner/Base.lproj/Main.storyboard", + "templates/app/ios.tmpl/Runner/Info.plist.tmpl", + "templates/app/ios.tmpl/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata", + "templates/app/ios.tmpl/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/app/ios.tmpl/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings", + "templates/app/ios.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme", + "templates/app/ios.tmpl/Runner.xcworkspace/contents.xcworkspacedata", + "templates/app/ios.tmpl/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/app/ios.tmpl/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings", + "templates/app/lib/main.dart.tmpl", + "templates/app/linux.tmpl/my_application.cc.tmpl", + "templates/app/linux.tmpl/.gitignore", + "templates/app/linux.tmpl/CMakeLists.txt.tmpl", + "templates/app/linux.tmpl/flutter/CMakeLists.txt", + "templates/app/linux.tmpl/main.cc", + "templates/app/linux.tmpl/my_application.cc", + "templates/app/linux.tmpl/my_application.h", + "templates/app/macos.tmpl/.gitignore", + "templates/app/macos.tmpl/Flutter/Flutter-Debug.xcconfig", + "templates/app/macos.tmpl/Flutter/Flutter-Release.xcconfig", + "templates/app/macos.tmpl/Runner/AppDelegate.swift", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png", + "templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json", + "templates/app/macos.tmpl/Runner/Base.lproj/MainMenu.xib", + "templates/app/macos.tmpl/Runner/Configs/AppInfo.xcconfig.tmpl", + "templates/app/macos.tmpl/Runner/Configs/Debug.xcconfig", + "templates/app/macos.tmpl/Runner/Configs/Release.xcconfig", + "templates/app/macos.tmpl/Runner/Configs/Warnings.xcconfig", + "templates/app/macos.tmpl/Runner/DebugProfile.entitlements", + "templates/app/macos.tmpl/Runner/Info.plist", + "templates/app/macos.tmpl/Runner/MainFlutterWindow.swift", + "templates/app/macos.tmpl/Runner/Release.entitlements", + "templates/app/macos.tmpl/Runner.xcodeproj/project.pbxproj.tmpl", + "templates/app/macos.tmpl/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/app/macos.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl", + "templates/app/macos.tmpl/Runner.xcworkspace/contents.xcworkspacedata", + "templates/app/macos.tmpl/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/app/projectName.iml.tmpl", + "templates/app/pubspec.yaml.tmpl", + "templates/app/README.md.tmpl", + "templates/app/test/widget_test.dart.tmpl", + "templates/app/web/favicon.png.copy.tmpl", + "templates/app/web/icons/Icon-192.png.copy.tmpl", + "templates/app/web/icons/Icon-512.png.copy.tmpl", + "templates/app/web/index.html.tmpl", + "templates/app/web/manifest.json.tmpl", + "templates/app/windows.tmpl/.gitignore", + "templates/app/windows.tmpl/CMakeLists.txt.tmpl", + "templates/app/windows.tmpl/flutter/CMakeLists.txt", + "templates/app/windows.tmpl/runner/CMakeLists.txt", + "templates/app/windows.tmpl/runner/flutter_window.cpp", + "templates/app/windows.tmpl/runner/flutter_window.h", + "templates/app/windows.tmpl/runner/main.cpp.tmpl", + "templates/app/windows.tmpl/runner/resource.h", + "templates/app/windows.tmpl/runner/resources/app_icon.ico.img.tmpl", + "templates/app/windows.tmpl/runner/runner.exe.manifest", + "templates/app/windows.tmpl/runner/Runner.rc.tmpl", + "templates/app/windows.tmpl/runner/run_loop.cpp", + "templates/app/windows.tmpl/runner/run_loop.h", + "templates/app/windows.tmpl/runner/utils.cpp", + "templates/app/windows.tmpl/runner/utils.h", + "templates/app/windows.tmpl/runner/win32_window.cpp", + "templates/app/windows.tmpl/runner/win32_window.h", + "templates/app/winuwp.tmpl/project_version", + "templates/cocoapods/Podfile-ios-objc", + "templates/cocoapods/Podfile-ios-swift", + "templates/cocoapods/Podfile-macos", + "templates/module/android/deferred_component/build.gradle.tmpl", + "templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl", + "templates/module/android/gradle/build.gradle.copy.tmpl", + "templates/module/android/gradle/gradle.properties.tmpl", + "templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl", + "templates/module/android/host_app_common/app.tmpl/src/main/AndroidManifest.xml.tmpl", + "templates/module/android/host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl", + "templates/module/android/host_app_common/app.tmpl/src/main/res/drawable/launch_background.xml", + "templates/module/android/host_app_common/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png", + "templates/module/android/host_app_common/app.tmpl/src/main/res/values/styles.xml", + "templates/module/android/host_app_editable/settings.gradle.copy.tmpl", + "templates/module/android/host_app_ephemeral/settings.gradle.copy.tmpl", + "templates/module/android/library/Flutter.tmpl/build.gradle.tmpl", + "templates/module/android/library/Flutter.tmpl/flutter.iml.copy.tmpl", + "templates/module/android/library/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl", + "templates/module/android/library/Flutter.tmpl/src/main/java/io/flutter/facade/Flutter.java.tmpl", + "templates/module/android/library/Flutter.tmpl/src/main/java/io/flutter/facade/FlutterFragment.java.tmpl", + "templates/module/android/library/include_flutter.groovy.copy.tmpl", + "templates/module/android/library/settings.gradle.copy.tmpl", + "templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl", + "templates/module/android/library_new_embedding/Flutter.tmpl/flutter.iml.copy.tmpl", + "templates/module/android/library_new_embedding/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl", + "templates/module/android/library_new_embedding/include_flutter.groovy.copy.tmpl", + "templates/module/android/library_new_embedding/settings.gradle.copy.tmpl", + "templates/module/common/.gitignore.tmpl", + "templates/module/common/.idea/libraries/Dart_SDK.xml.tmpl", + "templates/module/common/.idea/modules.xml.tmpl", + "templates/module/common/.idea/workspace.xml.tmpl", + "templates/module/common/.metadata.tmpl", + "templates/module/common/lib/main.dart.tmpl", + "templates/module/common/projectName.iml.tmpl", + "templates/module/common/projectName_android.iml.tmpl", + "templates/module/common/pubspec.yaml.tmpl", + "templates/module/common/README.md.tmpl", + "templates/module/common/test/widget_test.dart.tmpl", + "templates/module/ios/host_app_ephemeral/Config.tmpl/Debug.xcconfig", + "templates/module/ios/host_app_ephemeral/Config.tmpl/Flutter.xcconfig", + "templates/module/ios/host_app_ephemeral/Config.tmpl/Release.xcconfig", + "templates/module/ios/host_app_ephemeral/Flutter/engine/Flutter.podspec.tmpl", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/AppDelegate.h", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/AppDelegate.m", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Contents.json", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/Contents.json", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/README.md", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Base.lproj/LaunchScreen.storyboard", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Base.lproj/Main.storyboard", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/Info.plist.tmpl", + "templates/module/ios/host_app_ephemeral/Runner.tmpl/main.m", + "templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl", + "templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata", + "templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings", + "templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme", + "templates/module/ios/host_app_ephemeral/Runner.xcworkspace.tmpl/contents.xcworkspacedata", + "templates/module/ios/host_app_ephemeral/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist", + "templates/module/ios/host_app_ephemeral/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings", + "templates/module/ios/host_app_ephemeral_cocoapods/Config.tmpl/Debug.xcconfig", + "templates/module/ios/host_app_ephemeral_cocoapods/Config.tmpl/Release.xcconfig", + "templates/module/ios/host_app_ephemeral_cocoapods/Podfile.copy.tmpl", + "templates/module/ios/host_app_ephemeral_cocoapods/Runner.tmpl/AppDelegate.m", + "templates/module/ios/library/Flutter.tmpl/AppFrameworkInfo.plist", + "templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl", + "templates/module/ios/library/Flutter.tmpl/projectName.podspec.tmpl", + "templates/module/ios/library/Flutter.tmpl/README.md", + "templates/module/README.md", + "templates/package/.gitignore.tmpl", + "templates/package/.idea/libraries/Dart_SDK.xml.tmpl", + "templates/package/.idea/modules.xml.tmpl", + "templates/package/.idea/workspace.xml.tmpl", + "templates/package/.metadata.tmpl", + "templates/package/CHANGELOG.md.tmpl", + "templates/package/lib/projectName.dart.tmpl", + "templates/package/LICENSE.tmpl", + "templates/package/projectName.iml.tmpl", + "templates/package/pubspec.yaml.tmpl", + "templates/package/README.md.tmpl", + "templates/package/test/projectName_test.dart.tmpl", + "templates/plugin/.gitignore.tmpl", + "templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl", + "templates/plugin/.idea/modules.xml.tmpl", + "templates/plugin/.idea/runConfigurations/example_lib_main_dart.xml.tmpl", + "templates/plugin/.idea/workspace.xml.tmpl", + "templates/plugin/.metadata.tmpl", + "templates/plugin/android-java.tmpl/build.gradle.tmpl", + "templates/plugin/android-java.tmpl/projectName_android.iml.tmpl", + "templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl", + "templates/plugin/android-kotlin.tmpl/build.gradle.tmpl", + "templates/plugin/android-kotlin.tmpl/projectName_android.iml.tmpl", + "templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl", + "templates/plugin/android.tmpl/.gitignore", + "templates/plugin/android.tmpl/gradle/wrapper/gradle-wrapper.properties", + "templates/plugin/android.tmpl/gradle.properties.tmpl", + "templates/plugin/android.tmpl/settings.gradle.tmpl", + "templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl", + "templates/plugin/CHANGELOG.md.tmpl", + "templates/plugin/ios-objc.tmpl/Classes/pluginClass.h.tmpl", + "templates/plugin/ios-objc.tmpl/Classes/pluginClass.m.tmpl", + "templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl", + "templates/plugin/ios-swift.tmpl/Classes/pluginClass.h.tmpl", + "templates/plugin/ios-swift.tmpl/Classes/pluginClass.m.tmpl", + "templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl", + "templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl", + "templates/plugin/ios.tmpl/.gitignore", + "templates/plugin/ios.tmpl/Assets/.gitkeep", + "templates/plugin/lib/projectName.dart.tmpl", + "templates/plugin/LICENSE.tmpl", + "templates/plugin/linux.tmpl/CMakeLists.txt.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", + "templates/plugin/pubspec.yaml.tmpl", + "templates/plugin/README.md.tmpl", + "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/pluginClassSnakeCase.h.tmpl", + "templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl", + "templates/plugin/lib/projectName_web.dart.tmpl" + ] +} \ No newline at end of file