diff --git a/engine/src/flutter/shell/platform/fuchsia/dart_runner/dart_app.gni b/engine/src/flutter/shell/platform/fuchsia/dart_runner/dart_app.gni deleted file mode 100644 index c955eb81b0..0000000000 --- a/engine/src/flutter/shell/platform/fuchsia/dart_runner/dart_app.gni +++ /dev/null @@ -1,251 +0,0 @@ -# Copyright 2013 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("//build/package.gni") -import("//build/tools/json_merge/json_merge.gni") -import("//third_party/dart/build/dart/dart_action.gni") -import("//topaz/runtime/dart/config.gni") -import("//topaz/runtime/dart/dart_component.gni") -import("//topaz/runtime/dart/dart_kernel.gni") - -# Defines JIT runtime components to be further distributed in one package. -# -# Takes a set of dart components and puts them into one fuchsia package with -# the dart_jit_runner as its runtime. Also supports legacy calls where the -# components parameter isn't specified, in which we will create one default -# component for the package. -# -# Parameters -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# dart_package_name (optional) -# Name of the dart package for the component. If not provided, it will -# be inferred from the component name. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -# main_dart (required) -# File containing the main function of the application. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. This is used as an identifier in code that -# depends on the dart library that the *one and only* component generates. -# Only compatible when components is not specified (use -# components.dart_package_name). -# -template("_dart_jit_component") { - legacy_component = false - pkg_name = target_name - if (!defined(invoker.components)) { - # If components is not specified, we are fitting main_dart into a component - # scope, and using that for the package. - # - # TODO(CP-141): Remove support for legacy_component once all existing calls - # to dart_app() have a components parameter. - legacy_component = true - if (defined(invoker.fuchsia_package_name)) { - legacy_component_name = invoker.fuchsia_package_name - } else { - legacy_component_name = target_name - } - pkg_name = legacy_component_name - pkg_sources = [] - if (defined(invoker.sources)) { - pkg_sources = invoker.sources - } - components = [ - { - main_dart = invoker.main_dart - component_name = legacy_component_name - component_type = "dart" - package_root = "." - deps = invoker.deps - sources = pkg_sources - }, - ] - } - - flutter_dart_jit_component(target_name) { - forward_variables_from(invoker, "*") - } -} - -# Defines AOT runtime components to be further distributed in one package. -# -# Takes a set of dart components and puts them into one fuchsia package with -# the dart_aot_runner as its runtime. Also supports legacy calls where the -# components parameter isn't specified, in which we will create one default -# component for the package. -# -# Parameters -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# dart_package_name (optional) -# Name of the dart package for the component. If not provided, it will -# be inferred from the component name. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -# main_dart (required) -# File containing the main function of the application. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. This is used as an identifier in code that -# depends on the dart library that the *one and only* component generates. -# Only compatible when components is not specified (use -# components.dart_package_name). -# -template("_dart_aot_component") { - legacy_component = false - pkg_name = target_name - if (!defined(invoker.components)) { - # If components is not specified, we are fitting main_dart into a component - # scope, and using that for the package. - # - # TODO(CP-141): Remove support for legacy_component once all existing calls - # to dart_app() have a components parameter. - legacy_component = true - if (defined(invoker.fuchsia_package_name)) { - legacy_component_name = invoker.fuchsia_package_name - } else { - legacy_component_name = target_name - } - pkg_name = legacy_component_name - pkg_sources = [] - if (defined(invoker.sources)) { - pkg_sources = invoker.sources - } - components = [ - { - main_dart = invoker.main_dart - component_name = legacy_component_name - component_type = "dart" - package_root = "." - deps = invoker.deps - sources = pkg_sources - }, - ] - } - - flutter_dart_aot_component(target_name) { - forward_variables_from(invoker, "*") - } -} - -template("dart_jit_app") { - template_name = "_dart_jit_component" - if (dart_force_product) { - template_name = dart_product_app - } - - target(template_name, target_name) { - forward_variables_from(invoker, "*") - } -} - -template("dart_aot_app") { - template_name = "_dart_aot_component" - if (dart_force_product) { - template_name = dart_product_app - } - - target(template_name, target_name) { - forward_variables_from(invoker, "*") - } -} - -# Defines a Dart application that can be run in the Dart content handler -# -# Parameters -# -# main_dart (required) -# Name of the Dart file containing the main function. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. -# -# fuchsia_package_name (optional) -# Name of the Fuchsia package. -# -# deps (optional) -# List of Dart packages the application depends on. -# -# disable_analysis (optional) -# Prevents analysis from being run on this target. -# -# product (optional) -# A boolean. Whether to build/run the app in a stripped-down Dart VM. -# Defaults to !is_debug. -# -# resources (optional) -# Resources for the package (see //build/package.gni) -# -# tests (optional) -# List of tests forwarded for the package. See the definition in //build/package.gni. -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -template("dart_app") { - assert((defined(invoker.components) && !defined(invoker.main_dart)) || - (!defined(invoker.components) && defined(invoker.main_dart)), - "Only one of components or main_dart should be defined") - target(dart_default_app, target_name) { - forward_variables_from(invoker, "*") - } -} diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/flutter_app.gni b/engine/src/flutter/shell/platform/fuchsia/flutter/flutter_app.gni deleted file mode 100644 index a3a444256f..0000000000 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/flutter_app.gni +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright 2013 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. - -assert(is_fuchsia) - -import("//build/package.gni") -import("//build/tools/json_merge/json_merge.gni") -import("//third_party/dart/build/dart/dart_action.gni") -import("//topaz/runtime/dart/config.gni") -import("//topaz/runtime/dart/dart_component.gni") -import("//topaz/runtime/dart/dart_kernel.gni") - -# Defines JIT runtime components to be further distributed in one package. -# -# Takes a set of flutter components and puts them into one fuchsia package with -# the flutter_jit_runner as its runtime. Also supports legacy calls where the -# components parameter isn't specified, in which we will create one default -# component for the package. -# -# Parameters -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# dart_package_name (optional) -# Name of the dart package for the component. If not provided, it will -# be inferred from the component name. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -# main_dart (required) -# File containing the main function of the application. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. This is used as an identifier in code that -# depends on the dart library that the *one and only* component generates. -# Only compatible when components is not specified (use -# components.dart_package_name). -# -template("_flutter_jit_component") { - pkg_name = target_name - legacy_component = false - if (!defined(invoker.components)) { - # If components is not specified, we are fitting main_dart into a component - # scope, and using that for the package. - # - # TODO(CP-140): Remove support for legacy_component once all existing calls - # to flutter_app() have a components parameter. - legacy_component = true - if (defined(invoker.fuchsia_package_name)) { - legacy_component_name = invoker.fuchsia_package_name - } else { - legacy_component_name = target_name - } - pkg_name = legacy_component_name - pkg_sources = [] - if (defined(invoker.sources)) { - pkg_sources = invoker.sources - } - components = [ - { - main_dart = invoker.main_dart - component_name = legacy_component_name - component_type = "flutter" - package_root = "." - deps = invoker.deps - sources = pkg_sources - }, - ] - } - flutter_dart_jit_component(target_name) { - forward_variables_from(invoker, "*") - } -} - -# Defines AOT runtime components to be further distributed in one package. -# -# Takes a set of flutter components and puts them into one fuchsia package with -# the flutter_aot_runner as its runtime. Also supports legacy calls where the -# components parameter isn't specified, in which we will create one default -# component for the package. -# -# Parameters -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# dart_package_name (optional) -# Name of the dart package for the component. If not provided, it will -# be inferred from the component name. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -# main_dart (required) -# File containing the main function of the application. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. This is used as an identifier in code that -# depends on the dart library that the *one and only* component generates. -# Only compatible when components is not specified (use -# components.dart_package_name). -# -template("_flutter_aot_component") { - # This variable isn't needed in AOT builds. - if (defined(invoker.flutter_driver_extendable)) { - not_needed(invoker, [ "flutter_driver_extendable" ]) - } - pkg_name = target_name - legacy_component = false - if (!defined(invoker.components)) { - # If components is not specified, we are fitting main_dart into a component - # scope, and using that for the package. - # - # TODO(CP-140): Remove support for legacy_component once all existing calls - # to flutter_app() have a components parameter. - legacy_component = true - if (defined(invoker.fuchsia_package_name)) { - legacy_component_name = invoker.fuchsia_package_name - } else { - legacy_component_name = target_name - } - pkg_name = legacy_component_name - pkg_sources = [] - if (defined(invoker.sources)) { - pkg_sources = invoker.sources - } - components = [ - { - main_dart = invoker.main_dart - component_name = legacy_component_name - component_type = "flutter" - package_root = "." - deps = invoker.deps - sources = pkg_sources - }, - ] - } - flutter_dart_aot_component(target_name) { - forward_variables_from(invoker, "*") - } -} - -template("flutter_jit_app") { - template_name = "_flutter_jit_component" - if (dart_force_product) { - template_name = flutter_product_app - } - - target(template_name, target_name) { - forward_variables_from(invoker, "*") - } -} - -template("flutter_aot_app") { - template_name = "_flutter_aot_component" - if (dart_force_product) { - template_name = flutter_product_app - } - - target(template_name, target_name) { - forward_variables_from(invoker, "*", [ "space_dart" ]) - } -} - -# Defines a Flutter application -# -# Parameters -# -# main_dart (required) -# Name of the Dart file containing the main function. Either main_dart or -# components must be defined, but not both. -# -# package_name (optional) -# Name of the Dart package. -# -# fuchsia_package_name (optional) -# Name of the Fuchsia package. -# -# deps (optional) -# List of Dart packages the application depends on. -# -# manifest (optional) -# Path to the manifest file -# -# disable_analysis (optional) -# Prevents analysis from being run on this target. -# -# product (optional) -# A boolean. Whether to build/run the app in a stripped-down Dart VM. -# Defaults to !is_debug. -# -# flutter_driver_extendable (optional) -# A boolean. Determines if, in a debug build, this package will be built -# with a wrapper that auto-enables flutter driver extensions when running -# the application in a an environment that includes TestRunner. Does not -# affect AOT or release builds. -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -template("flutter_app") { - assert((defined(invoker.components) && !defined(invoker.main_dart)) || - (!defined(invoker.components) && defined(invoker.main_dart)), - "Only one of components or main_dart should be defined") - target(flutter_default_app, target_name) { - forward_variables_from(invoker, "*", [ "aot" ]) - } -} - -# Defines multiple Flutter/Dart applications in one fuchsia package -# -# Parameters -# -# components (required) -# [list of scopes] Defines the components in the package. Either main_dart -# or components must be defined, but not both. -# -# Entries in a scope in the resources list: -# -# component_name (required) -# Name of the component. -# -# main_dart (required) -# File containing the main function of the component. -# -# dart_package_name (optional) -# Name of the dart package for the component. If not provided, it will -# be inferred from the component name. -# -# package_root (optional) -# Path to the dart package for the component. If not provided, it will -# be assumed as ".". -# -# sources (optional) -# Relative path of source files to be included in the dart package for -# the component at $package_root/lib. -# -template("flutter_dart_apps") { - assert(defined(invoker.components) && !defined(invoker.main_dart), - "components must be defined. Use main_dart under components instead.") - - pkg_name = target_name - legacy_component = false - if (flutter_default_app == "flutter_jit_app") { - template_name = "flutter_dart_jit_component" - if (dart_force_product) { - template_name = flutter_product_app - } - target(template_name, target_name) { - forward_variables_from(invoker, "*", [ "aot" ]) - } - } else { - template_name = "flutter_dart_aot_component" - if (dart_force_product) { - template_name = flutter_product_app - } - target(template_name, target_name) { - forward_variables_from(invoker, - "*", - [ - "aot", - "space_dart", - ]) - } - } -} diff --git a/engine/src/flutter/tools/executable_action.gni b/engine/src/flutter/tools/executable_action.gni new file mode 100644 index 0000000000..abdfbb224a --- /dev/null +++ b/engine/src/flutter/tools/executable_action.gni @@ -0,0 +1,43 @@ +# Copyright 2013 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. + +if (host_os == "win") { + host_executable_suffix = ".exe" +} else { + host_executable_suffix = "" +} + +template("executable_action") { + action(target_name) { + assert(defined(invoker.tool), "The executable tool must be specified.") + assert(defined(invoker.args), "The command line args must be specified.") + if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + + script = "//build/gn_run_binary.py" + + host_executable = + rebase_path("${invoker.tool}${host_executable_suffix}", root_build_dir) + + if (defined(invoker.deps)) { + deps = invoker.deps + } else { + deps = [] + } + + if (defined(invoker.inputs)) { + inputs = invoker.inputs + } else { + inputs = [] + } + + outputs = invoker.outputs + + args = [ host_executable ] + invoker.args + } +} diff --git a/engine/src/flutter/tools/fuchsia/copy_debug_symbols.py b/engine/src/flutter/tools/fuchsia/copy_debug_symbols.py index cc415eb59d..61c48b0fdc 100755 --- a/engine/src/flutter/tools/fuchsia/copy_debug_symbols.py +++ b/engine/src/flutter/tools/fuchsia/copy_debug_symbols.py @@ -99,9 +99,12 @@ def main(): help='Path to read-elf executable.') args = parser.parse_args() - assert os.path.exists(args.exec_path) - assert os.path.exists(args.dest) - assert os.path.exists(args.read_elf) + assert os.path.exists(args.exec_path), ( + 'exec_path "%s" does not exist' % args.exec_path) + assert os.path.exists(args.dest), ( + 'dest "%s" does not exist' % args.dest) + assert os.path.exists(args.read_elf), ( + 'read_elf "%s" does not exist' % args.read_elf) parts = GetBuildIdParts(args.exec_path, args.read_elf) dbg_prefix_base = os.path.join(args.dest, parts['prefix_dir']) diff --git a/engine/src/flutter/tools/fuchsia/dart.gni b/engine/src/flutter/tools/fuchsia/dart.gni index 2455974a76..534acb52ea 100644 --- a/engine/src/flutter/tools/fuchsia/dart.gni +++ b/engine/src/flutter/tools/fuchsia/dart.gni @@ -1,10 +1,12 @@ -# Copyright 2018 The Fuchsia Authors. All rights reserved. +# Copyright 2013 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. gen_snapshot = "//third_party/dart/runtime/bin:gen_snapshot" gen_snapshot_product = "//third_party/dart/runtime/bin:gen_snapshot_product" +prebuilt_dart = "//third_party/dart/tools/sdks/dart-sdk/bin/dart" + observatory_target = "//third_party/dart/runtime/observatory:observatory_archive" observatory_archive_dir = get_label_info(observatory_target, "target_gen_dir") diff --git a/engine/src/flutter/tools/fuchsia/dart/BUILD.gn b/engine/src/flutter/tools/fuchsia/dart/BUILD.gn new file mode 100644 index 0000000000..80bc8ac871 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/toolchain.gni") +import("//flutter/tools/fuchsia/python/python_binary.gni") +import("//flutter/tools/fuchsia/sdk/sdk_targets.gni") +import("//flutter/tools/fuchsia/toolchain/basic_toolchain.gni") + +python_binary("gen_dart_package_config") { + main_source = "gen_dart_package_config.py" +} + +if (current_toolchain == default_toolchain) { + # A toolchain dedicated to processing and analyzing Dart packages. + # The only targets in this toolchain are action() targets, so it + # has no real tools. But every toolchain needs stamp and copy. + basic_toolchain("dartlang") { + expected_label = dart_toolchain + } +} + +if (current_toolchain != default_toolchain) { + sdk_targets("dart_library") { + meta = "$fuchsia_sdk_path/meta/manifest.json" + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/config.gni b/engine/src/flutter/tools/fuchsia/dart/config.gni new file mode 100644 index 0000000000..8b66a75892 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/config.gni @@ -0,0 +1,24 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/dart_build_config.gni") + +declare_args() { + # Forces all Dart apps to build in product mode which is a + # stripped down version of the VM running in AOT mode. + dart_force_product = false + + # TODO(fxbug.dev/64153) renable aot builds + # if (dart_force_product) { + # Product AOT + # dart_default_build_cfg = dart_release_build_cfg + # } else if (is_debug) { + # Non-product JIT + dart_default_build_cfg = dart_debug_build_cfg + + # } else { + # Non-product AOT + # dart_default_build_cfg = dart_profile_build_cfg + # } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/dart.gni b/engine/src/flutter/tools/fuchsia/dart/dart.gni new file mode 100644 index 0000000000..b615563c1a --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/dart.gni @@ -0,0 +1,22 @@ +# Copyright 2013 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. + +# NOTE: Generally, the build rules in //flutter/tools/fuchsia/dart/, +# //flutter/tools/fuchsia/dart/kernel/, and similar SUBDIRECTORIES of +# //flutter/tools/fuchsia/ (such as //flutter/tools/fuchsia/flutter/) +# mirror the directory paths and much of the build rule content of fuchsia.git +# //build/... +# +# Most of the fuchsia-derived build rules were implemented after some similar +# build rules already existed in the directory //flutter/tools/fuchsia/ +# and several existing build targets use these (legacy) build rules. +# Though some rules and targets in //flutter/tools/fuchsia/ have similar names +# to the fuchsia.git-derived rules, the rule structures and behavior can be +# different. Therefore both are maintained for now. +# +# In the case of this file--dart.gni--some existing definitions from the +# original dart.gni are relevant and useful to the new fuchsia.git-derived +# rules, so these definitions can simply be imported. + +import("//flutter/tools/fuchsia/dart.gni") diff --git a/engine/src/flutter/tools/fuchsia/dart/dart_build_config.gni b/engine/src/flutter/tools/fuchsia/dart/dart_build_config.gni new file mode 100644 index 0000000000..e5116e80d8 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/dart_build_config.gni @@ -0,0 +1,51 @@ +# Copyright 2013 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. + +# Builds the component in a non-product JIT build. This will +# launch the vm service in the runner. +dart_debug_build_cfg = { + runtime_meta = "//flutter/shell/platform/fuchsia/dart_runner/meta/jit_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/dart_runner:dart_jit_runner" + platform_name = "dart_runner" + is_aot = false + is_product = false + enable_asserts = true +} + +# Builds the component in a non-product AOT build. This will +# launch the vm service in the runner. +# This configuration is not compatible with a --release build since the +# profile aot runner is built without asserts. +dart_aot_debug_build_cfg = { + runtime_meta = "//flutter/shell/platform/fuchsia/dart_runner/meta/aot_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/dart_runner:dart_aot_runner" + platform_name = "dart_runner" + is_aot = true + is_product = false + enable_asserts = true +} + +# Builds the component in a non-product AOT build. This will +# launch the vm service in the runner. +dart_profile_build_cfg = { + runtime_meta = "//flutter/shell/platform/fuchsia/dart_runner/meta/aot_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/dart_runner:dart_aot_runner" + platform_name = "dart_runner" + is_aot = true + is_product = false + enable_asserts = false +} + +# Builds the component in a product AOT build. This will +# not launch the vm service in the runner. +dart_release_build_cfg = { + runtime_meta = + "//flutter/shell/platform/fuchsia/dart_runner/meta/aot_product_runtime" + runner_dep = + "//flutter/shell/platform/fuchsia/dart_runner:dart_aot_product_runner" + platform_name = "dart_runner" + is_aot = true + is_product = true + enable_asserts = false +} diff --git a/engine/src/flutter/tools/fuchsia/dart/dart_library.gni b/engine/src/flutter/tools/fuchsia/dart/dart_library.gni new file mode 100644 index 0000000000..7062823e34 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/dart_library.gni @@ -0,0 +1,345 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/dart.gni") +import("//flutter/tools/fuchsia/dart/dart_package_config.gni") +import("//flutter/tools/fuchsia/dart/toolchain.gni") +import("//flutter/tools/python/python3_action.gni") + +# Defines a Dart library +# +# Parameters +# +# sources +# The list of all sources in this library. +# These sources must be within source_dir. +# +# package_root (optional) +# Path to the directory hosting the library. +# This is useful for generated content, and can be ignored otherwise. +# Defaults to ".". +# +# package_name (optional) +# Name of the Dart package. This is used as an identifier in code that +# depends on this library. +# +# language_version (optional) +# Specify the Dart language version to use for this package. +# If language_version is not specified but pubspec is then the language +# version will be read from the pubspec. If no language version can be +# determined then we will default to version "2.8". +# It is recommended to specify a language_version if it is well known +# instead of relying on the pubspec file since this will improve compilation +# times. +# +# infer_package_name (optional) +# Infer the package name based on the path to the package. +# +# NOTE: Exactly one of package_name or infer_package_name must be set. +# +# source_dir (optional) +# Path to the directory containing the package sources, relative to +# package_root. All non third-party dart files under source_dir must be +# included in sources. +# Defaults to "lib". +# +# deps (optional) +# List of labels this library depends on. +# +# TODO(fxb/63133): non_dart_deps is deprecated. Use deps instead. +# non_dart_deps (optional, deprecated) +# List of labels this library depends on that are not Dart libraries. This +# includes things like actions that generate Dart code. It typically doesn't +# need to be set. +# Note that these labels *must* have an explicit toolchain attached. +# +# TODO(fxbug.dev/71902): set up allowlist for disable_source_verification when +# dart_test no longer depends on dart_library. +# NOTE: Do NOT disable source verification unless you are 100% sure it is +# absolutely necessary. +# disable_source_verification (optional) +# Prevents source verification from being run on this target. +# +# extra_sources (optional) +# Additional sources to consider for analysis. +# +# pubspec (optional) +# Path to the pubspec.yaml. If not provided, will default to looking for +# the pubspec.yaml in the package root. It is not common that this value will +# need to be set but can be useful for generated code. +# +# options_file (optional) +# Path to the analysis_options.yaml file. If not provided, will default to +# looking for the analysis_options.yaml in the package root. It is not common +# that this value needs to be set but can be useful for generated code. +# +# disable_metadata_entry (optional) +# Prevents metedata entry from being written to the dart_packag_config json file. +# +# null_safe (optional) +# A flag that enables null safety check in dart libraries. +# +# Example of usage: +# +# dart_library("baz") { +# package_name = "foo.bar.baz" +# +# sources = [ +# "blah.dart", +# ] +# +# deps = [ +# "//foo/bar/owl", +# ] +# } +if (current_toolchain == dart_toolchain) { + template("dart_library") { + forward_variables_from(invoker, + [ + "visibility", + "hermetic_deps", + ]) + + if (defined(invoker.package_name)) { + package_name = invoker.package_name + } else if (defined(invoker.infer_package_name) && + invoker.infer_package_name) { + # Compute a package name from the label: + # //foo/bar --> foo.bar + # //foo/bar:blah --> foo.bar._blah + # //garnet/public/foo/bar --> foo.bar + # Strip public directories. + full_dir = get_label_info(":$target_name", "dir") + package_name = full_dir + package_name = string_replace(package_name, "//", "") + package_name = string_replace(package_name, "/", ".") + + # If the last directory name does not match the target name, add the + # target name to the resulting package name. + name = get_label_info(":$target_name", "name") + last_dir = get_path_info(full_dir, "name") + if (last_dir != name) { + package_name = "$package_name._$name" + } + } else { + assert(false, "Must specify either a package_name or infer_package_name") + } + + _dart_deps = [] + if (defined(invoker.deps)) { + foreach(dep, invoker.deps) { + _dart_deps += [ get_label_info(dep, "label_no_toolchain") ] + } + } + + _non_dart_deps = [] + if (defined(invoker.non_dart_deps)) { + _non_dart_deps += invoker.non_dart_deps + } + + package_root = "." + if (defined(invoker.package_root)) { + package_root = invoker.package_root + } + + source_dir = "$package_root/lib" + if (defined(invoker.source_dir)) { + source_dir = "$package_root/${invoker.source_dir}" + } + + assert(defined(invoker.sources), "Sources must be defined") + + disable_source_verification = + defined(invoker.disable_source_verification) && + invoker.disable_source_verification + if (disable_source_verification && invoker.sources == []) { + not_needed([ source_dir ]) + } + + rebased_sources = [] + foreach(source, invoker.sources) { + rebased_source_dir = rebase_path(source_dir) + rebased_sources += [ "$rebased_source_dir/$source" ] + } + if (defined(invoker.extra_sources)) { + foreach(source, invoker.extra_sources) { + rebased_sources += [ rebase_path(source) ] + } + } + source_file = "$target_gen_dir/$target_name.sources" + write_file(source_file, rebased_sources, "list lines") + + # Dependencies of the umbrella group for the targets in this file. + group_deps = [] + + _public_deps = [] + if (defined(invoker.public_deps)) { + _public_deps = invoker.public_deps + } + + _metadata = { + package_config_entries = [ + { + name = package_name + if (defined(invoker.language_version)) { + language_version = invoker.language_version + } else if (defined(invoker.null_safe) && invoker.null_safe) { + language_version = "2.12" + } else if (defined(invoker.pubspec)) { + pubspec_path = rebase_path(invoker.pubspec, root_build_dir) + } + root_uri = rebase_path(package_root, root_build_dir) + if (defined(invoker.source_dir)) { + package_uri = invoker.source_dir + } else { + package_uri = "lib" + } + }, + ] + dart_build_info = [ + { + __is_current_target = false + __package_name = package_name + __deps = _dart_deps + _non_dart_deps + __public_deps = _public_deps + __rebased_sources = rebased_sources + }, + ] + + dart_build_info_barrier = [] + } + + # When we generate a package_config for the analyzer we need to make sure + # that we are including this library in that file. The dart_package_config + # collects metadata from its dependencies so we create this group to expose + # that data. We also expose this in the group target below so that users of + # the dart_package_config target can just add the targets to the deps list. + _publish_metadata_target_name = "${target_name}_package_metadata" + group(_publish_metadata_target_name) { + metadata = _metadata + } + + _dart_package_config_target_name = "${target_name}_dart_package" + _packages_path = "$target_gen_dir/${target_name}_package_config.json" + dart_package_config(_dart_package_config_target_name) { + # Do not publish the metadata to the dart_package_config json file if the + # disable_metadata_entry flag is enabled in the dart_tool. The reason this is here + # is to avoid entries that may have identical rootUris as fxb/58781 has highlighted. + deps = _dart_deps + if (!defined(invoker.disable_metadata_entry) || + !invoker.disable_metadata_entry) { + deps += [ ":$_publish_metadata_target_name" ] + } + public_deps = _non_dart_deps + outputs = [ _packages_path ] + forward_variables_from(invoker, [ "testonly" ]) + } + group_deps += [ ":$_dart_package_config_target_name" ] + + ################################ + # Dart source "verification" + # + # Warn if there are dart sources from the source directory that are + # not explicitly part of sources. This may cause a failure when syncing to + # another repository, as they will be excluded from the resulting BUILD + # file. + # + # Also warn if nonexistent files are included in sources. + if (!disable_source_verification) { + source_verification_target_name = "${target_name}_source_verification" + python3_action(source_verification_target_name) { + script = "//flutter/tools/fuchsia/dart/verify_sources.py" + output_file = "$target_gen_dir/$target_name.missing" + + sources = rebased_sources + outputs = [ output_file ] + + args = [ + "--source_dir", + rebase_path(source_dir), + "--stamp", + rebase_path(output_file), + ] + invoker.sources + + forward_variables_from(invoker, [ "testonly" ]) + + # Deps may include codegen dependencies that generate dart sources. + deps = _dart_deps + _non_dart_deps + } + group_deps += [ ":$source_verification_target_name" ] + } + + # Generate a file that lists files containing full (including all direct and + # transitive dependencies) sources for this target's dependencies. + _all_deps_sources_list_target = "${target_name}.all_deps_sources.list" + _all_deps_sources_list_file = + "${target_gen_dir}/${target_name}.all_deps_sources.list" + generated_file(_all_deps_sources_list_target) { + forward_variables_from(invoker, [ "testonly" ]) + outputs = [ _all_deps_sources_list_file ] + data_keys = [ "all_deps_sources" ] + walk_keys = [ "all_deps_sources_barrier" ] + deps = _dart_deps + } + + # Generate full sources for this target by combining sources of this target + # with full sources of all dependencies. + # + # The generated file contains sources of this target and all of its direct + # and transitive dependencies. + # + # The output file is useful when writing depfiles for actions like dart + # analyzer, which recursively reads all sources. + _all_deps_sources_target = "${target_name}.all_deps_sources" + _all_deps_sources_file = "${target_gen_dir}/${target_name}.all_deps_sources" + python3_action(_all_deps_sources_target) { + forward_variables_from(invoker, [ "testonly" ]) + + script = "//flutter/tools/fuchsia/dart/merge_deps_sources.py" + + outputs = [ _all_deps_sources_file ] + depfile = "${_all_deps_sources_file}.d" + args = [ + "--output", + rebase_path(outputs[0], root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + "--source_lists", + "@" + rebase_path(_all_deps_sources_list_file, root_build_dir), + "--sources", + ] + rebase_path(rebased_sources, root_build_dir) + + inputs = [ _all_deps_sources_list_file ] + deps = [ ":${_all_deps_sources_list_target}" ] + metadata = { + all_deps_sources = [ rebase_path(outputs[0], root_build_dir) ] + all_deps_sources_barrier = [] + } + } + group_deps += [ ":${_all_deps_sources_target}" ] + + not_needed(invoker, [ "options_file" ]) + + group(target_name) { + # _dart_deps are added here to ensure they are fully built. + # Up to this point, only the targets generating .packages had been + # depended on. + deps = _dart_deps + _non_dart_deps + + public_deps = group_deps + + metadata = _metadata + forward_variables_from(invoker, [ "testonly" ]) + } + } +} else { # Not the Dart toolchain. + template("dart_library") { + group(target_name) { + forward_variables_from(invoker, [ "testonly" ]) + not_needed(invoker, "*") + + public_deps = [ ":$target_name($dart_toolchain)" ] + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/dart_package_config.gni b/engine/src/flutter/tools/fuchsia/dart/dart_package_config.gni new file mode 100644 index 0000000000..6072c61de4 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/dart_package_config.gni @@ -0,0 +1,117 @@ +# Copyright 2013 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. + +# Generates a package_config.json file containing all of the packages used +# to generate a kernel file. +# +# Package configs are files which describe metadata about a dart package. +# This includes information like the name, dart language version, where to +# find the files, etc. The file is required by the dart kernel compiler. +# +# Parameters +# +# deps, public_deps (optional) +# [list of labels] The targets to generate a manifest for. +# See `gn help` for more details. +# +# testonly, visibility, metadata (optional) +# See `gn help`. +# +# outputs (optional) +# Singleton list containing the path to the package_config file. +# Defaults to `[ "$target_gen_dir/${target_name}_package_config.json" ]`. +template("dart_package_config") { + main_target = target_name + generate_target = "${target_name}_generate" + + # Build the name of the output file. + if (defined(invoker.outputs)) { + _outputs = invoker.outputs + assert(_outputs != [] && _outputs == [ _outputs[0] ], + "Outputs list must have exactly one element.") + package_config_file = _outputs[0] + } else { + package_config_file = "$target_gen_dir/${target_name}_package_config.json" + } + intermediate_file = "$package_config_file.partial" + + # Gather metadata about runtime objects. + generated_file(generate_target) { + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + ]) + + visibility = [ ":$main_target" ] + + data_keys = [ + # A list of package configuration entries + # + # Each entry must contain these values: + # - `name`: The name of the package; + # - `root_uri`: Path to the package root relative to root_build_dir; + # - `package_uri`: The path in which the source lives + # + # The following entries may optionally be specified + # - `language_version`: The dart language version. + # - `pubspec_path`: The path to the pubspec file to find the language version + "package_config_entries", + ] + walk_keys = [ "package_config_entry_barrier" ] + + outputs = [ intermediate_file ] + + output_conversion = "json" + } + + # Converts the intermediate to a real pacakge_config.json file. + action(main_target) { + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + "visibility", + ]) + + script = + get_label_info("//flutter/tools/fuchsia/dart:gen_dart_package_config", + "target_out_dir") + "/gen_dart_package_config.pyz" + + inputs = [ intermediate_file ] + outputs = [ package_config_file ] + depfile = "${target_gen_dir}/${target_name}.d" + + args = [ + "--input", + rebase_path(intermediate_file, root_build_dir), + "--output", + rebase_path(package_config_file, root_build_dir), + "--root", + rebase_path("//", root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + ] + + if (!defined(deps)) { + deps = [] + } + deps += [ + ":$generate_target", + "//flutter/tools/fuchsia/dart:gen_dart_package_config", + ] + + metadata = { + # Add a barrier here to avoid double of inclusion of elements listed in + # the generated package config. + package_config_entry_barrier = [] + + if (defined(invoker.metadata)) { + forward_variables_from(invoker.metadata, "*") + } + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/dart_tool.gni b/engine/src/flutter/tools/fuchsia/dart/dart_tool.gni new file mode 100644 index 0000000000..6afb46aa4c --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/dart_tool.gni @@ -0,0 +1,105 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/dart/dart_package_config.gni") +import("//flutter/tools/fuchsia/dart/toolchain.gni") + +import("//flutter/tools/python/python3_action.gni") + +# Wraps a dart snapshot in a script to be invoked by dart +# +# Parameters +# +# dart (required) +# The path to the dart binary +# +# snapshot (required) +# The path to the dart snapshot +# +# deps (optional) +# Dependencies of this application +# +# output_name (optional) +# Name of the output file to generate. Defaults to $target_name. +template("_dart_snapshot_invocation") { + assert(defined(invoker.dart), "Must specify the path to the dart binary") + assert(defined(invoker.snapshot), + "Must specify the path to the dart snapshot") + + if (defined(invoker.output_name)) { + app_name = invoker.output_name + } else { + app_name = target_name + } + + # Builds a convenience script to invoke the app. + python3_action(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "deps", + ]) + + script = "//flutter/tools/fuchsia/dart/gen_app_invocation.py" + + app_path = "$root_out_dir/dart-tools/$app_name" + dart_binary = invoker.dart + snapshot = invoker.snapshot + + inputs = [ + dart_binary, + snapshot, + ] + outputs = [ app_path ] + + args = [ + "--out", + rebase_path(app_path, root_build_dir), + + # `--dart` and `--snapshot` are used in the output app script, use + # absolute path so the script would work regardless where it's invoked. + "--dart", + rebase_path(dart_binary), + "--snapshot", + rebase_path(snapshot), + ] + + metadata = { + # Record metadata for the //:tool_paths build API. + tool_paths = [ + { + cpu = current_cpu + label = get_label_info(":$target_name", "label_with_toolchain") + name = app_name + os = current_os + path = rebase_path(app_path, root_build_dir) + }, + ] + snapshot_path = [ rebase_path(snapshot, root_build_dir) ] + } + } +} + +# Defines a Dart application that can be run on the host which is +# compiled from an existing snapshot +# +# Parameters +# +# snapshot (required) +# The path to the dart snapshot +# +# deps (optional) +# Dependencies of this application +# +# output_name (optional) +# Name of the output file to generate. Defaults to $target_name. +template("dart_prebuilt_tool") { + assert(defined(invoker.snapshot), + "Must specify the path to the dart snapshot") + _dart_snapshot_invocation(target_name) { + dart = prebuilt_dart + forward_variables_from(invoker, "*") + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/fidl_dart.gni b/engine/src/flutter/tools/fuchsia/dart/fidl_dart.gni new file mode 100644 index 0000000000..43d40ad678 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/fidl_dart.gni @@ -0,0 +1,95 @@ +# Copyright 2013 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/executable_action.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/dart/toolchain.gni") +import("//flutter/tools/fuchsia/fidl/fidl.gni") +import("//flutter/tools/fuchsia/fidl/toolchain.gni") + +# Generates some Dart bindings for a FIDL library. +# +# The parameters for this template are defined in //flutter/tools/fuchsia/fidl/fidl.gni. The +# relevant parameters in this template are: +# - name; + +template("fidl_dart") { + assert(current_toolchain == dart_toolchain, + "This template can only be used in the $dart_toolchain toolchain.") + + not_needed(invoker, [ "sources" ]) + + main_target_name = target_name + generation_target_name = "${target_name}_dart_generate" + + library_name = target_name + root_dir = "$target_gen_dir/${library_name}_package" + if (defined(invoker.name)) { + library_name = invoker.name + root_dir = "$target_gen_dir/${target_name}_${library_name}_package" + } + bindings_dir = "$root_dir/lib" + async_bindings_file = "$bindings_dir/fidl_async.dart" + test_bindings_file = "$bindings_dir/fidl_test.dart" + + fidl_target_gen_dir = + get_label_info(":bogus($fidl_toolchain)", "target_gen_dir") + json_representation = "$fidl_target_gen_dir/$target_name.fidl.json" + + executable_action(generation_target_name) { + visibility = [ ":*" ] + + tool = "${fuchsia_sdk_path}/tools/fidlgen_dart" + + inputs = [ json_representation ] + + outputs = [ + async_bindings_file, + test_bindings_file, + ] + + args = [ + "--json", + rebase_path(json_representation, root_build_dir), + "--output-async", + rebase_path(async_bindings_file, root_build_dir), + "--output-test", + rebase_path(test_bindings_file, root_build_dir), + ] + + deps = [ ":$main_target_name($fidl_toolchain)" ] + forward_variables_from(invoker, [ "testonly" ]) + } + + dart_library(main_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + + package_root = root_dir + + package_name = "fidl_" + string_replace(library_name, ".", "_") + + null_safe = true + + sources = [ + rebase_path(async_bindings_file, bindings_dir), + rebase_path(test_bindings_file, bindings_dir), + ] + + deps = [ ":$generation_target_name" ] + + if (defined(invoker.public_deps)) { + deps += invoker.public_deps + } + + # invoker.deps are the non_fidl_deps passed in to the fidl() rule + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/gen_app_invocation.py b/engine/src/flutter/tools/fuchsia/dart/gen_app_invocation.py new file mode 100755 index 0000000000..013ad26339 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/gen_app_invocation.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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 argparse +import os +import stat +import string +import sys + + +def main(): + parser = argparse.ArgumentParser( + description='Generate a script that invokes a Dart application') + parser.add_argument( + '--out', help='Path to the invocation file to generate', required=True) + parser.add_argument('--dart', help='Path to the Dart binary', required=True) + parser.add_argument( + '--snapshot', help='Path to the app snapshot', required=True) + args = parser.parse_args() + + app_file = args.out + app_path = os.path.dirname(app_file) + if not os.path.exists(app_path): + os.makedirs(app_path) + + script_template = string.Template( + '''#!/bin/sh + +$dart \\ + $snapshot \\ + "$$@" +''') + with open(app_file, 'w') as file: + file.write(script_template.substitute(args.__dict__)) + permissions = ( + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | + stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH) + os.chmod(app_file, permissions) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/dart/gen_dart_package_config.py b/engine/src/flutter/tools/fuchsia/dart/gen_dart_package_config.py new file mode 100755 index 0000000000..4eaa22dc6e --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/gen_dart_package_config.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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. +"""Reads the contents of a package config file generated by the build and + converts it to a real package_config.json file +""" + +import argparse +import collections +import json +import os +import re +import sys +import yaml + +DEFAULT_LANGUAGE_VERSION = '2.8' + +Package = collections.namedtuple( + 'Package', ['name', 'rootUri', 'languageVersion', 'packageUri']) + + +class PackageConfig: + # The version of the package config. + VERSION = 2 + + # The name of the generator which gets written to the json output + GENERATOR_NAME = os.path.basename(__file__) + + def __init__(self, packages): + self.packages = packages + + def asdict(self): + """Converts the package config to a dictionary""" + return { + 'configVersion': self.VERSION, + 'packages': [p._asdict() for p in sorted(self.packages)], + 'generator': self.GENERATOR_NAME, + } + + +def language_version_from_pubspec(pubspec): + """Parse the content of a pubspec.yaml""" + with open(pubspec) as pubspec: + parsed = yaml.safe_load(pubspec) + if not parsed: + return DEFAULT_LANGUAGE_VERSION + + # If a format like sdk: '>=a.b' or sdk: 'a.b' is found, we'll use a.b. + # In all other cases we default to "2.8" + env_sdk = parsed.get('environment', {}).get('sdk', 'any') + match = re.search(r'^(>=)?((0|[1-9]\d*)\.(0|[1-9]\d*))', env_sdk) + if match: + min_sdk_version = match.group(2) + else: + min_sdk_version = DEFAULT_LANGUAGE_VERSION + + return min_sdk_version + + +def collect_packages(items, relative_to): + """Reads metadata produced by GN to create lists of packages and pubspecs. + - items: a list of objects collected from gn + - relative_to: The directory which the packages are relative to. This is + the location that contains the package_config.json file + + Returns None if there was a problem parsing packages + """ + packages = [] + pubspec_paths = [] + for item in items: + if 'language_version' in item: + language_version = item['language_version'] + elif 'pubspec_path' in item: + pubspec_paths.append(item['pubspec_path']) + language_version = language_version_from_pubspec( + item['pubspec_path']) + else: + language_version = DEFAULT_LANGUAGE_VERSION + + package = Package( + name=item['name'], + rootUri=os.path.relpath(item['root_uri'], relative_to), + languageVersion=language_version, + packageUri=item['package_uri']) + + # TODO(fxbug.dev/56428): enable once we sort out our duplicate packages + # for p in packages: + # if p.rootUri == package.rootUri: + # print('Failed to create package_config.json file') + # print('The following packages contain the same package root ' + p.rootUri) + # print(' - ' + p.rootUri) + # print(' - ' + package.rootUri) + # return None + + packages.append(package) + + return packages, pubspec_paths + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--input', help='Path to original package_config', required=True) + parser.add_argument( + '--output', help='Path to the updated package_config', required=True) + parser.add_argument('--root', help='Path to fuchsia root', required=True) + parser.add_argument('--depfile', help='Path to the depfile', required=True) + args = parser.parse_args() + + with open(args.input, 'r') as input_file: + contents = json.load(input_file) + + output_dir = os.path.dirname(os.path.abspath(args.output)) + packages, pubspec_paths = collect_packages(contents, output_dir) + if packages is None: + return 1 + + with open(args.depfile, 'w') as depfile: + depfile.write('%s: %s' % (args.output, ' '.join(pubspec_paths))) + + with open(args.output, 'w') as output_file: + package_config = PackageConfig(packages) + json.dump( + package_config.asdict(), + output_file, + indent=2, + sort_keys=True, + separators=(',', ': ')) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/dart/kernel/BUILD.gn b/engine/src/flutter/tools/fuchsia/dart/kernel/BUILD.gn new file mode 100644 index 0000000000..d8a538e393 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/kernel/BUILD.gn @@ -0,0 +1,9 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/dart_tool.gni") + +dart_prebuilt_tool("gen_kernel") { + snapshot = "//third_party/dart/pkg/vm/bin/gen_kernel.dart" +} diff --git a/engine/src/flutter/tools/fuchsia/dart/kernel/convert_manifest_to_json.py b/engine/src/flutter/tools/fuchsia/dart/kernel/convert_manifest_to_json.py new file mode 100755 index 0000000000..84bb32c8d7 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/kernel/convert_manifest_to_json.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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. +'''Reads the contents of a kernel manifest generated by the build and + converts it to a format suitable for distribution_entries +''' + +import argparse +import collections +import json +import os +import sys + +Entry = collections.namedtuple('Entry', ['source', 'destination']) + + +def collect(lines): + '''Reads the kernel manifest and creates an array of Entry objects. + - lines: a list of lines from the manifest + ''' + entries = [] + for line in lines: + values = line.split("=", 1) + entries.append(Entry(source=values[1], destination=values[0])) + + return entries + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--input', help='Path to original manifest', required=True) + parser.add_argument( + '--output', help='Path to the updated json file', required=True) + args = parser.parse_args() + + with open(args.input, 'r') as input_file: + contents = input_file.read().splitlines() + + entries = collect(contents) + + if entries is None: + return 1 + + with open(args.output, 'w') as output_file: + json.dump( + [e._asdict() for e in entries], + output_file, + indent=2, + sort_keys=True, + separators=(',', ': ')) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/dart/kernel/dart_kernel.gni b/engine/src/flutter/tools/fuchsia/dart/kernel/dart_kernel.gni new file mode 100644 index 0000000000..832871a1c3 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/kernel/dart_kernel.gni @@ -0,0 +1,329 @@ +# Copyright 2013 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. + +# NOTE: Generally, the build rules in //flutter/tools/fuchsia/dart/, +# //flutter/tools/fuchsia/dart/kernel/, and similar SUBDIRECTORIES of +# //flutter/tools/fuchsia/ (such as //flutter/tools/fuchsia/flutter/) +# mirror the directory paths and much of the build rule content of fuchsia.git +# //build/... +# +# Most of the fuchsia-derived build rules were implemented after some similar +# build rules already existed in the directory //flutter/tools/fuchsia/ +# and several existing build targets use these (legacy) build rules. +# Though some rules and targets in //flutter/tools/fuchsia/ have similar names +# to the fuchsia.git-derived rules, the rule structures and behavior can be +# different. Therefore both are maintained for now. +# +# This file--dart_kernel.gni--and its primary template--dart_kernel()--match +# pre-existing (legacy) template and filename in +# //flutter/tools/fuchsia/dart_kernel.gni. +# +# The templates appear to have different behavior, so both will be maintained +# until we can determine a better way to reconcile them. + +import("//flutter/tools/fuchsia/dart/config.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/dart/toolchain.gni") + +import("//flutter/tools/python/python3_action.gni") + +_gen_kernel_label = + "//flutter/tools/fuchsia/dart/kernel:gen_kernel($host_toolchain)" +_gen_kernel_path = + get_label_info(_gen_kernel_label, "root_out_dir") + "/dart-tools/gen_kernel" + +flutter_platform_name = "flutter_runner" + +# Converts the kernel manifest file from fini format to JSON format and +# registers the metadata for the fuchsia_package to pick up. +# +# Parameters +# +# manifest (required) +# Path to the kernel manifest +# Type: Path +template("_convert_kernel_manifest") { + assert(defined(invoker.manifest), "dart_kernel() requires a manifest") + + python3_action(target_name) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + "visibility", + ]) + _converted_file_name = + "${target_gen_dir}/${target_name}_kernel_manifest.json" + + script = "//flutter/tools/fuchsia/dart/kernel/convert_manifest_to_json.py" + args = [ + "--input", + rebase_path(invoker.manifest, root_build_dir), + "--output", + rebase_path(_converted_file_name, root_build_dir), + ] + + sources = [ invoker.manifest ] + + outputs = [ _converted_file_name ] + + metadata = { + distribution_entries = [ + { + file = rebase_path(_converted_file_name, root_build_dir) + label = get_label_info(target_name, "label_with_toolchain") + }, + ] + } + } +} + +# Generates dill files for a Dart application. +# +# It is not likely that this target will be used directly. Instead, developers +# should use the dart_tool or dart/flutter_component targets which create a +# kernel for you. +# +# Parameters +# +# platform_name (required) +# The name of the platform, either "flutter_runner" or "dart_runner". +# +# packages_path (required) +# Path to the package_config.json file. +# +# main_dart (required, mutually exclusive) +# Path to Dart source file containing main(). Mutually exclusive with +# main_dart_file. This is relative to source_dir, should exist in the +# main_package, and uses a package: URI. +# +# main_package (required, mutually exclusive) +# The name of the package which contains main. Mutually exclusive with +# main_dart_file. +# +# main_dart_file (required, mutually exclusive) +# Path to Dart source file containing main(). Mutually exclusive with +# main_dart. This doesn't need to necessarily exist in main_package and uses +# a fuchsia-source: URI. +# +# product (required) +# Whether this should be built with the product runner. +# +# is_aot required) +# Whether this kernel is an aot build. +# +# generate_manifest (optional) +# Whether the compiler should generate a kernel manifest containing the list +# of partial dill files in JIT builds. This flag is ignored in aot builds +# Default: false +# +# kernel_path (optional) +# The path to the kernel output. Defaults to target_gen_dir/${target_name}.dil +# +# kernel_target_name (optional) +# The name of the kernel target. This parameter is required if you are +# depending on the kernel_path for an input. This allows you to establish a +# dependency chain on the generated file. +# +# args (optional) +# A list of additional arguments to the compiler.dart program in this +# directory that generates the kernel files. +# +# link_platform (optional) +# Whether the kernel compiler should link the current platform.dill into +# the build. If false, the --no-link-platform flag will be passed to the +# compiler. Defaults to false +template("dart_kernel") { + assert(defined(invoker.platform_name), "dart_kernel() requires platform_name") + assert(defined(invoker.packages_path), + "dart_kernel() requires the path to the package config") + assert( + (defined(invoker.main_dart) && defined(invoker.main_package)) != + defined(invoker.main_dart_file), + "dart_kernel() requires either (main_dart and main_package) or main_dart_file") + assert(defined(invoker.product), "dart_kernel() requires product") + assert(defined(invoker.is_aot), "dart_kernel() requires is_aot") + + if (defined(invoker.kernel_target_name)) { + _kernel_target_name = invoker.kernel_target_name + } else { + _kernel_target_name = "${target_name}_kernel" + } + _group_deps = [ ":$_kernel_target_name" ] + + _kernel_deps = [] + if (defined(invoker.deps)) { + _kernel_deps += invoker.deps + } + + _generate_manifest = false + if (invoker.is_aot) { + not_needed(invoker, [ "generate_manifest" ]) + } else { + if (defined(invoker.generate_manifest) && invoker.generate_manifest) { + _generate_manifest = true + } + } + + if (_generate_manifest) { + _kernel_manifest = "$target_gen_dir/${target_name}.dilpmanifest" + + _convert_target_name = "${target_name}_convert_kernel_manifest" + _convert_kernel_manifest(_convert_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + manifest = _kernel_manifest + + # must depend on the kernel target so we have the kernel manifest + deps = [ ":$_kernel_target_name" ] + } + + _group_deps += [ ":$_convert_target_name" ] + } + + python3_action(_kernel_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + + if (defined(invoker.kernel_path)) { + _kernel_path = invoker.kernel_path + } else { + _kernel_path = + "$target_gen_dir/__untraced_dart_kernel__/${target_name}.dil" + } + + depfile = "${_kernel_path}.d" + + _kernel_deps += [ _gen_kernel_label ] + + if (invoker.platform_name == flutter_platform_name) { + _kernel_deps += [ + "//flutter/shell/platform/fuchsia/flutter/kernel:kernel_platform_files", + ] + _platform_path = "$root_out_dir/flutter_runner_patched_sdk" + } else { + assert(false, + "platform_name must be either dart_runner or flutter_runner") + } + + _platform_strong_dill = "${_platform_path}/platform_strong.dill" + inputs = [ + _gen_kernel_path, + _platform_strong_dill, + invoker.packages_path, + ] + outputs = [ _kernel_path ] + + if (_generate_manifest) { + outputs += [ + # Explicit output when using --manifest. + _kernel_manifest, + + # Implicit output when using --manifest; see createManifest in compiler.dart. + _kernel_manifest + ".dilplist", + _kernel_manifest + ".frameworkversion", + ] + } + + _multi_root_scheme = "fuchsia-source" + + # Rebase against // instead of root_build_dir since the package_config is + # relative to the sources. + _rebased_packages_path = rebase_path(invoker.packages_path, "//") + + # gen_kernel writes absolute paths to depfiles, convert them to relative. + # See more information in https://fxbug.dev/75451. + script = "//flutter/tools/fuchsia/depfile_path_to_relative.py" + args = [ + "--depfile=" + rebase_path(depfile, root_build_dir), + "--", + rebase_path(_gen_kernel_path, root_out_dir), + ] + if (defined(invoker.args)) { + args += invoker.args + } + args += [ + "--verbosity=warning", + "--no-sound-null-safety", + "--target", + invoker.platform_name, + "--platform", + rebase_path(_platform_strong_dill, root_build_dir), + "--filesystem-scheme", + _multi_root_scheme, + "--filesystem-root", + rebase_path("//"), + "--packages", + "$_multi_root_scheme:///$_rebased_packages_path", + + # Repeating "--depfile" because the previous one is for + # `depfile_path_to_relative.py`, and this one is for `_gen_kernel_path`. + "--depfile", + rebase_path(depfile, root_build_dir), + "--output", + rebase_path(_kernel_path, root_build_dir), + ] + + if (_generate_manifest) { + args += [ + "--split-output-by-packages", + "--manifest", + rebase_path(_kernel_manifest, root_build_dir), + ] + } + + if (is_debug) { + args += [ "--embed-sources" ] + } else { + args += [ "--no-embed-sources" ] + } + + if (invoker.is_aot) { + args += [ + "--aot", + "--tfa", + ] + } else if (!defined(invoker.link_platform) || !invoker.link_platform) { + args += [ "--no-link-platform" ] + } + + if (invoker.product) { + # Setting this flag in a non-product release build for AOT (a "profile" + # build) causes the vm service isolate code to be tree-shaken from an app. + # See the pragma on the entrypoint here: + # + # This define excludes debugging and profiling code from Flutter. + args += [ "-Ddart.vm.product=true" ] + } else { + if (!is_debug) { + # The following define excludes debugging code from Flutter. + args += [ "-Ddart.vm.profile=true" ] + } + } + + if (defined(invoker.main_dart)) { + args += [ "package:${invoker.main_package}/${invoker.main_dart}" ] + } else { + rebased_main_dart = rebase_path(invoker.main_dart_file, "//") + args += [ "$_multi_root_scheme:///$rebased_main_dart" ] + } + + deps = _kernel_deps + } + + group(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + deps = _group_deps + } +} diff --git a/engine/src/flutter/tools/fuchsia/dart/merge_deps_sources.py b/engine/src/flutter/tools/fuchsia/dart/merge_deps_sources.py new file mode 100755 index 0000000000..a84b427ddf --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/merge_deps_sources.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3.8 +"""Merges sources of a Dart target and its dependencies""" + +# Copyright 2013 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 argparse +import json +import os +import sys + + +def main(): + parser = argparse.ArgumentParser( + 'Merges sources of a Dart target and its dependencies', + fromfile_prefix_chars='@') + parser.add_argument( + '--output', + help='Path to output the final list', + type=argparse.FileType('w'), + required=True) + parser.add_argument( + '--depfile', + help='Path to the depfile to generate', + type=argparse.FileType('w'), + required=True) + parser.add_argument( + '--sources', + help='Sources of this target', + nargs='*', + ) + parser.add_argument( + '--source_lists', + help='Files containing lists of Dart sources', + nargs='*') + args = parser.parse_args() + + args.depfile.write( + '{}: {}\n'.format(args.output.name, ' '.join(args.source_lists))) + + # Merges sources of this target, and all of its dependencies. + all_sources = set(args.sources) + for f in args.source_lists: + with open(f, 'r') as f: + all_sources.update(json.load(f)) + json.dump(sorted(all_sources), args.output) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/dart/toolchain.gni b/engine/src/flutter/tools/fuchsia/dart/toolchain.gni new file mode 100644 index 0000000000..1fe54f506c --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/toolchain.gni @@ -0,0 +1,10 @@ +# Copyright 2013 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_toolchain = "//flutter/tools/fuchsia/dart:dartlang" + +dart_root_gen_dir = get_label_info("//bogus($dart_toolchain)", "root_gen_dir") +# In order to access the target_gen_dir in the Dart toolchain from some location +# in the source tree, use the following: +# dart_target_gen_dir = get_label_info(":bogus($dart_toolchain)", "target_gen_dir") diff --git a/engine/src/flutter/tools/fuchsia/dart/verify_sources.py b/engine/src/flutter/tools/fuchsia/dart/verify_sources.py new file mode 100755 index 0000000000..94607421bf --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dart/verify_sources.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3.8 + +# Copyright 2013 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 argparse +import os +import sys +import pathlib + + +def main(): + parser = argparse.ArgumentParser( + "Verifies that all .dart files are included in sources, and sources don't include nonexsitent files" + ) + parser.add_argument( + "--source_dir", + help="Path to the directory containing the package sources", + required=True) + parser.add_argument( + "--stamp", + help="File to touch when source checking succeeds", + required=True) + parser.add_argument( + "sources", help="source files", nargs=argparse.REMAINDER) + args = parser.parse_args() + + actual_sources = set() + # Get all dart sources from source directory. + src_dir_path = pathlib.Path(args.source_dir) + for (dirpath, dirnames, filenames) in os.walk(src_dir_path, topdown=True): + relpath_to_src_root = pathlib.Path(dirpath).relative_to(src_dir_path) + actual_sources.update( + os.path.normpath(relpath_to_src_root.joinpath(filename)) + for filename in filenames + if pathlib.Path(filename).suffix == ".dart") + + expected_sources = set(args.sources) + # It is possible for sources to include dart files outside of source_dir. + actual_sources.update( + [ + s for s in (expected_sources - actual_sources) + if src_dir_path.joinpath(s).resolve().exists() + ], + ) + + if actual_sources == expected_sources: + with open(args.stamp, "w") as stamp: + stamp.write("Success!") + return 0 + + def sources_to_abs_path(sources): + return sorted(str(src_dir_path.joinpath(s)) for s in sources) + + missing_sources = actual_sources - expected_sources + if missing_sources: + print( + '\nSource files found that were missing from the "sources" parameter:\n{}\n' + .format("\n".join(sources_to_abs_path(missing_sources))), + ) + nonexistent_sources = expected_sources - actual_sources + if nonexistent_sources: + print( + '\nSource files listed in "sources" parameter but not found:\n{}\n'. + format("\n".join(sources_to_abs_path(nonexistent_sources))), + ) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/depfile_path_to_relative.py b/engine/src/flutter/tools/fuchsia/depfile_path_to_relative.py new file mode 100755 index 0000000000..8d4f09bbca --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/depfile_path_to_relative.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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 argparse +import os +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser( + description= + 'Executes a command, then rewrites the depfile, converts all absolute paths to relative' + ) + parser.add_argument( + '--depfile', help='Path to the depfile to rewrite', required=True) + parser.add_argument( + 'command', nargs='+', help='Positional args for the command to run') + args = parser.parse_args() + + retval = subprocess.call(args.command) + if retval != 0: + return retval + + lines = [] + with open(args.depfile, 'r') as f: + for line in f: + lines.append(' '.join(os.path.relpath(p) for p in line.split())) + with open(args.depfile, 'w') as f: + f.write('\n'.join(lines)) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/dist/resource.gni b/engine/src/flutter/tools/fuchsia/dist/resource.gni new file mode 100644 index 0000000000..825cbd831b --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/dist/resource.gni @@ -0,0 +1,330 @@ +# Copyright 2013 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. + +# Declare data files to be accessible at runtime on the target device. +# +# A resource() target looks just like a copy() target but $outputs are +# relative target paths. Using $data_deps to this resource() target in +# each target whose code uses $outputs at runtime ensures that the files +# will be present on the system. +# +# If the file is not in the source tree, it should be generated by another +# target in the build listed in $deps. If that would be a generated_file() +# target, then use generated_resource() instead of resource(). +# +# Parameters +# +# data_deps +# Optional: Additional dependencies for the runtime image. These are +# included in the image if this target is, but are not related to the +# $sources list. +# Type: list(label) +# +# deps +# Optional: Targets that produce $sources. Any files listed in +# $sources that are produced by the build should be produced by a +# target listed here. This is the only thing that guarantees those +# files will have been built by the time the image is being packed. +# Targets reached only via this $deps list will *not* contribute their +# own contents to the image directly. For that, list them in $data_deps. +# Targets listed here are used only to produce the $sources files. +# Type: list(label) +# +# outputs +# Required: List of one runtime path. This must be a relative path (no +# leading `/`). It can use placeholders based on $sources; see copy() +# and `gn help source_expansion`. When this resource() target is in +# the dependency graph of a zbi() target, then this is the path within +# the BOOTFS, which appears at /boot in the namespace of early-boot and +# standalone Zircon processes. +# Type: list(path) +# +# sources +# Required: List of files in the source tree or build that become $outputs. +# See copy() for details. +# Type: list(file) +# +# See copy() for other parameters. +template("resource") { + if (invoker.sources != []) { + _label = get_label_info(":$target_name", "label_with_toolchain") + } + + group(target_name) { + forward_variables_from(invoker, + "*", + [ + "metadata", + "outputs", + "sources", + ]) + metadata = { + # Used by the distribution_manifest() template. + distribution_entries_barrier = [] + distribution_entries = [] + + # Used by the zbi() template. + zbi_input_barrier = [] + + if (defined(invoker.metadata)) { + forward_variables_from(invoker.metadata, "*") + } + + # Stop *_manifest() and zbi_test() from picking up files or + # zbi_input() items from the deps, but let them reach the data_deps. + if (defined(data_deps)) { + distribution_entries_barrier += data_deps + zbi_input_barrier += data_deps + } + + foreach(source, invoker.sources) { + foreach(target, process_file_template([ source ], invoker.outputs)) { + assert(rebase_path(target, "foo") != target, + "`outputs` in resource() cannot start with /") + distribution_entries += [ + { + source = rebase_path(source, root_build_dir) + destination = target + label = _label + }, + ] + } + } + } + } +} + +# Declare data files to be accessible at runtime on the target device. +# +# Similar to resource() but with a different interface that allows grouping +# multiple files into a single target when their packaged paths don't follow +# a common pattern. +# +# Parameters +# +# data_deps +# Optional: Additional dependencies for the runtime image. These are +# included in the image if this target is, but are not related to the +# $sources list. +# Type: list(label) +# +# deps +# Optional: Targets that produce $sources. Any files listed in +# $sources that are produced by the build should be produced by a +# target listed here. This is the only thing that guarantees those +# files will have been built by the time the image is being packed. +# Targets reached only via this $deps list will *not* contribute their +# own contents to the image directly. For that, list them in $data_deps. +# Targets listed here are used only to produce the $sources files. +# Type: list(label) +# +# files +# Required: List of scopes containing `source` and `dest` paths. +# `source` paths are local file paths. +# `dest` paths are packaged paths. +# For instance: +# files = [ +# { +# source = "//path/to/file.txt" +# dest = "data/first.txt" +# }, +# { +# source = "//path/to/other_file.txt", +# dest = "data/second.txt" +# }, +# ] +# Type: list(scope) +# +# testonly, visibility +template("resource_group") { + if (invoker.files != []) { + _label = get_label_info(":$target_name", "label_with_toolchain") + } + + group(target_name) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + "visibility", + ]) + metadata = { + # Used by the distribution_manifest() template. + distribution_entries_barrier = [] + distribution_entries = [] + + # Used by the zbi() template. + zbi_input_barrier = [] + + if (defined(invoker.metadata)) { + forward_variables_from(invoker.metadata, "*") + } + + # Stop *_manifest() and zbi_test() from picking up files or + # zbi_input() items from the deps, but let them reach the data_deps. + if (defined(data_deps)) { + distribution_entries_barrier += data_deps + zbi_input_barrier += data_deps + } + + foreach(file, invoker.files) { + distribution_entries += [ + { + source = rebase_path(file.source, root_build_dir) + destination = file.dest + label = _label + }, + ] + } + } + } +} + +# resource_tree() is similar to resource() but makes it easy to replicate +# a tree of sources files, relative to a given sources path prefix, to a +# given destination directory. +# +# For example: +# +# //some/dir/BUILD.gn: +# resource_trees("my-resources") { +# sources_root = "res" +# sources = [ +# foo.resource", +# bar/bar-1.resource", +# ] +# dest_dir = "data/resources" +# } +# +# Will declare the following installation requirements: +# +# //some/dir/res/foo.resource --> data/resources/foo.resource +# //some/dir/res/bar/bar.resource --> data/resources/bar/bar.resource +# +# This is difficult to do with resource() because GN source expansion +# cannot preserve the original sources input paths. +# +# This template is useful to avoid calling resource() multiple times +# in a loop when dealing with resource files laid out into different +# sub-directories. Note that the files cannot be renamed though! +# +# Parameters +# +# source_root +# Optional: A path prefix that is prepended to all items in the 'sources' +# list. If not specified, the current target's directory is used. +# Type: string(path) +# +# sources +# Required: List of files in the source tree or build that will be +# installed. Each 'file' item in this list is installed to +# '$dest_dir/$file', and its content taken from '$sources_root/$file'. +# Note that unlike resource(), there is no way to transform or expand +# source paths. +# Type: list(file) +# +# dest_dir +# Required: Destination path where all sources are installed. +# Cannot start with a "/". Use an empty string to install files directory +# to the package's top-level directory. +# Type: string(path) +# +# data_deps +# Optional: Additional dependencies for the runtime image. These are +# included in the image if this target is, but are not related to the +# $sources list. +# Type: list(label) +# +# deps +# Optional: Targets that produce $sources. Any files listed in +# $sources that are produced by the build should be produced by a +# target listed here. This is the only thing that guarantees those +# files will have been built by the time the image is being packed. +# Targets reached only via this $deps list will *not* contribute their +# own contents to the image directly. For that, list them in $data_deps. +# Targets listed here are used only to produce the $sources files. +# Type: list(label) +# +template("resource_tree") { + assert(defined(invoker.sources), "sources is required!") + assert(defined(invoker.dest_dir), "dest_dir is required!") + dest_dir = invoker.dest_dir + if (dest_dir != "") { + # Sanitize dest_dir and append a directory separator. + assert(rebase_path(dest_dir, "foo") != dest_dir, + "dest_dir cannot start with /: $dest_dir") + assert(dest_dir != "." && dest_dir != ".." && + string_replace(dest_dir, "./", "") == dest_dir, + "dest_dir cannot contain . or .. path elements!: $dest_dir") + dest_dir = string_replace(dest_dir + "/", "//", "/") + } + + if (defined(invoker.sources_root)) { + assert(invoker.sources_root != "", "sources_root cannot be empty!") + sources_prefix = invoker.sources_root + + # Append trailing separator if needed + if (string_replace(sources_prefix + "###", "/###", "") == + sources_prefix + "###") { + sources_prefix += "/" + } + } else { + sources_prefix = "" + } + + target_label = get_label_info(":$target_name", "label_with_toolchain") + if (invoker.sources == []) { + # Support resource_tree() targets with empty sources list. + not_needed(invoker, + [ + "dest_dir", + "sources_root", + ]) + not_needed([ + "sources_prefix", + "dest_dir", + "target_label", + ]) + } + + group(target_name) { + forward_variables_from(invoker, + "*", + [ + "dest_dir", + "metadata", + "sources", + "sources_root", + ]) + metadata = { + # Used by the distribution_manifest() template. + distribution_entries_barrier = [] + distribution_entries = [] + + # Used by the zbi() template. + zbi_input_barrier = [] + + if (defined(invoker.metadata)) { + forward_variables_from(invoker.metadata, "*") + } + + # Stop *_manifest() and zbi_test() from picking up files or + # zbi_input() items from the deps, but let them reach the data_deps. + if (defined(data_deps)) { + distribution_entries_barrier += data_deps + zbi_input_barrier += data_deps + } + + foreach(_source, invoker.sources) { + distribution_entries += [ + { + source = rebase_path(sources_prefix + _source, root_build_dir) + destination = dest_dir + _source + label = target_label + }, + ] + } + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/fidl/BUILD.gn b/engine/src/flutter/tools/fuchsia/fidl/BUILD.gn new file mode 100644 index 0000000000..0855b3f0bc --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/fidl/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright 2013 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/fidl/fidl.gni") +import("//flutter/tools/fuchsia/fidl/toolchain.gni") +import("//flutter/tools/fuchsia/sdk/sdk_targets.gni") +import("//flutter/tools/fuchsia/toolchain/basic_toolchain.gni") + +# A toolchain dedicated to processing FIDL libraries. +# The only targets in this toolchain are action() targets, so it +# has no real tools. But every toolchain needs stamp and copy. +if (current_toolchain == default_toolchain) { + basic_toolchain("fidling") { + expected_label = fidl_toolchain + } +} + +if (current_toolchain != default_toolchain) { + sdk_targets("fidl_library") { + meta = "$fuchsia_sdk_path/meta/manifest.json" + } +} diff --git a/engine/src/flutter/tools/fuchsia/fidl/fidl.gni b/engine/src/flutter/tools/fuchsia/fidl/fidl.gni new file mode 100644 index 0000000000..4a313b1223 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/fidl/fidl.gni @@ -0,0 +1,46 @@ +# Copyright 2013 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/dart/toolchain.gni") +import("//flutter/tools/fuchsia/fidl/toolchain.gni") + +# Declares a FIDL library. +# +# Depending on the toolchain in which this targets is expanded, it will yield +# different results: +# - in the FIDL toolchain, it will compile its source files into an +# intermediate representation consumable by language bindings generators; +# - in the target or shared toolchain, this will produce a source_set +# containing language-specific bindings. +template("fidl") { + # Allow generated targets visibility to their dependant generated targets + if (defined(invoker.visibility)) { + invoker.visibility += [ ":*" ] + } + + assert(!defined(invoker.deps), + "All FIDL dependencies are inherently " + + "public, use 'public_deps' instead of 'deps'.") + + deps = [] + + if (current_toolchain == dart_toolchain) { + import("//flutter/tools/fuchsia/dart/fidl_dart.gni") + + fidl_dart(target_name) { + forward_variables_from(invoker, "*") + } + } else { + if (current_toolchain == fidl_toolchain) { + import("//flutter/tools/fuchsia/fidl/fidl_library.gni") + + fidl_library(target_name) { + forward_variables_from(invoker, "*") + } + } else { + not_needed("*") + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/fidl/fidl_library.gni b/engine/src/flutter/tools/fuchsia/fidl/fidl_library.gni new file mode 100644 index 0000000000..4f10e6d32d --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/fidl/fidl_library.gni @@ -0,0 +1,143 @@ +# Copyright 2013 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("//flutter/tools/executable_action.gni") +import("//flutter/tools/fuchsia/fidl/fidl.gni") +import("//flutter/tools/fuchsia/fidl/toolchain.gni") + +# Generates some representation of a FIDL library that's consumable by Language +# bindings generators. +# +# The parameters for this template are defined in //flutter/tools/fuchsia/fidl/fidl.gni. The +# relevant parameters in this template are: +# - name; +# - sources. + +template("fidl_library") { + assert( + current_toolchain == fidl_toolchain, + "This template can only be used in the FIDL toolchain $fidl_toolchain.") + + assert(defined(invoker.sources), "A FIDL library requires some sources.") + + library_name = target_name + if (defined(invoker.name)) { + library_name = invoker.name + } + + response_file = "$target_gen_dir/$target_name.args" + fidl_stem = "$target_gen_dir/$target_name.fidl" + json_representation = "$fidl_stem.json" + + coding_tables = "$fidl_stem.tables.c" + + main_target_name = target_name + response_file_target_name = "${target_name}_response_file" + compilation_target_name = "${target_name}_compile" + + # Only artifacts that have various associated FIDL generated targets. + fidl_deps = [] + + # Artifacts other than FIDL, that are also dependencies. + non_fidl_deps = [] + + if (defined(invoker.non_fidl_deps)) { + non_fidl_deps += invoker.non_fidl_deps + } + + if (defined(invoker.deps)) { + fidl_deps += invoker.deps - non_fidl_deps + } + if (defined(invoker.public_deps)) { + fidl_deps += invoker.public_deps + } + + action(response_file_target_name) { + visibility = [ ":*" ] + + script = "//flutter/tools/fuchsia/fidl/gen_response_file.py" + + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "sources", + "testonly", + ]) + + libraries = "$target_gen_dir/$main_target_name.libraries" + + outputs = [ + response_file, + libraries, + ] + + args = [ + "--out-response-file", + rebase_path(response_file, root_build_dir), + "--out-libraries", + rebase_path(libraries, root_build_dir), + "--json", + rebase_path(json_representation, root_build_dir), + "--tables", + rebase_path(coding_tables, root_build_dir), + "--name", + library_name, + "--sources", + ] + rebase_path(sources, root_build_dir) + + if (fidl_deps != []) { + dep_libraries = [] + + foreach(dep, fidl_deps) { + gen_dir = get_label_info(dep, "target_gen_dir") + name = get_label_info(dep, "name") + dep_libraries += [ "$gen_dir/$name.libraries" ] + } + + inputs = dep_libraries + + args += [ "--dep-libraries" ] + rebase_path(dep_libraries, root_build_dir) + } + } + + executable_action(compilation_target_name) { + forward_variables_from(invoker, [ "testonly" ]) + + visibility = [ ":*" ] + + tool = "${fuchsia_sdk_path}/tools/fidlc" + + inputs = [ response_file ] + + outputs = [ + coding_tables, + json_representation, + ] + + args = [ "@" + rebase_path(response_file, root_build_dir) ] + + deps = [ ":$response_file_target_name" ] + non_fidl_deps + } + + group(main_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + "response_file", + ]) + + # Metadata to allow us to query all FIDL IR files. + metadata = { + fidl_json = [ rebase_path(json_representation, root_build_dir) ] + generated_sources = fidl_json + } + + public_deps = [ + ":$compilation_target_name", + ":$response_file_target_name", + ] + } +} diff --git a/engine/src/flutter/tools/fuchsia/fidl/gen_response_file.py b/engine/src/flutter/tools/fuchsia/fidl/gen_response_file.py new file mode 100755 index 0000000000..6e4035d43b --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/fidl/gen_response_file.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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 argparse +import os +import string +import sys + + +def read_libraries(libraries_path): + with open(libraries_path) as f: + lines = f.readlines() + return [l.rstrip("\n") for l in lines] + + +def write_libraries(libraries_path, libraries): + directory = os.path.dirname(libraries_path) + if not os.path.exists(directory): + os.makedirs(directory) + with open(libraries_path, "w+") as f: + for library in libraries: + f.write(library) + f.write("\n") + + +def main(): + parser = argparse.ArgumentParser( + description="Generate response file for FIDL frontend") + parser.add_argument( + "--out-response-file", + help="The path for for the response file to generate", + required=True) + parser.add_argument( + "--out-libraries", + help="The path for for the libraries file to generate", + required=True) + parser.add_argument( + "--json", help="The path for the JSON file to generate, if any") + parser.add_argument( + "--tables", help="The path for the tables file to generate, if any") + parser.add_argument( + "--deprecated-fuchsia-only-c-client", + help="The path for the C simple client file to generate, if any") + parser.add_argument( + "--deprecated-fuchsia-only-c-header", + help="The path for the C header file to generate, if any") + parser.add_argument( + "--deprecated-fuchsia-only-c-server", + help="The path for the C simple server file to generate, if any") + parser.add_argument( + "--name", help="The name for the generated FIDL library, if any") + parser.add_argument( + "--depfile", help="The name for the generated depfile, if any") + parser.add_argument( + "--sources", help="List of FIDL source files", nargs="*") + parser.add_argument( + "--dep-libraries", help="List of dependent libraries", nargs="*") + parser.add_argument( + "--experimental-flag", + help="List of experimental flags", + action="append") + args = parser.parse_args() + + target_libraries = [] + + for dep_libraries_path in args.dep_libraries or []: + dep_libraries = read_libraries(dep_libraries_path) + for library in dep_libraries: + if library in target_libraries: + continue + target_libraries.append(library) + + target_libraries.append(" ".join(sorted(args.sources))) + write_libraries(args.out_libraries, target_libraries) + + response_file = [] + + response_file.append('--experimental allow_new_syntax') + + if args.json: + response_file.append("--json %s" % args.json) + + if args.tables: + response_file.append("--tables %s" % args.tables) + + if args.deprecated_fuchsia_only_c_client: + response_file.append( + "--deprecated-fuchsia-only-c-client %s" % + args.deprecated_fuchsia_only_c_client) + + if args.deprecated_fuchsia_only_c_header: + response_file.append( + "--deprecated-fuchsia-only-c-header %s" % + args.deprecated_fuchsia_only_c_header) + + if args.deprecated_fuchsia_only_c_server: + response_file.append( + "--deprecated-fuchsia-only-c-server %s" % + args.deprecated_fuchsia_only_c_server) + + if args.name: + response_file.append("--name %s" % args.name) + + if args.depfile: + response_file.append("--depfile %s" % args.depfile) + + if args.experimental_flag: + for experimental_flag in args.experimental_flag: + response_file.append("--experimental %s" % experimental_flag) + + response_file.extend( + ["--files %s" % library for library in target_libraries]) + + with open(args.out_response_file, "w+") as f: + f.write(" ".join(response_file)) + f.write("\n") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/fidl/toolchain.gni b/engine/src/flutter/tools/fuchsia/fidl/toolchain.gni new file mode 100644 index 0000000000..503c5b6688 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/fidl/toolchain.gni @@ -0,0 +1,5 @@ +# Copyright 2013 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. + +fidl_toolchain = "//flutter/tools/fuchsia/fidl:fidling" diff --git a/engine/src/flutter/tools/fuchsia/flutter/config.gni b/engine/src/flutter/tools/fuchsia/flutter/config.gni new file mode 100644 index 0000000000..fa474f33de --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/flutter/config.gni @@ -0,0 +1,24 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/flutter/flutter_build_config.gni") + +declare_args() { + # If set to true, will force the runners to be built in + # product mode which means they will not have an exposed vm service + flutter_force_product = false +} + +declare_args() { + if (flutter_force_product) { + # Product AOT + flutter_default_build_cfg = flutter_release_build_cfg + } else if (is_debug) { + # Non-product JIT + flutter_default_build_cfg = flutter_debug_build_cfg + } else { + # Non-product AOT + flutter_default_build_cfg = flutter_profile_build_cfg + } +} diff --git a/engine/src/flutter/tools/fuchsia/flutter/flutter_build_config.gni b/engine/src/flutter/tools/fuchsia/flutter/flutter_build_config.gni new file mode 100644 index 0000000000..00cdc8611b --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/flutter/flutter_build_config.gni @@ -0,0 +1,56 @@ +# Copyright 2013 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. + +# Non-product JIT is "debug". It launches the vm service. +# Non-product AOT is "profile". It also launches the vm service, but lacks tools that rely on JIT. +# Product AOT is "release". It doesn't launch the vm service. + +# Builds the component in a non-product JIT build. This will +# launch the vm service in the runner. +flutter_debug_build_cfg = { + runtime_meta = "//flutter/shell/platform/fuchsia/flutter/meta/jit_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/flutter:jit" + platform_name = "flutter_runner" + is_aot = false + is_product = false + enable_asserts = true +} + +# Builds the component in a non-product AOT build. This will +# launch the vm service in the runner. +# This configuration is not compatible with a --release build since the +# profile aot runner is built without asserts. +flutter_aot_debug_build_cfg = { + runtime_meta = "//flutter/shell/platform/fuchsia/flutter/meta/aot_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/flutter:aot" + platform_name = "flutter_runner" + is_aot = true + is_product = false + enable_asserts = true +} + +# Builds the component in a non-product AOT build. This will +# launch the vm service in the runner. +flutter_profile_build_cfg = { + runtime_meta = + "//flutter/shell/platform/fuchsia/flutter/meta/aot_runtime" # profile + # runner + runner_dep = "//flutter/shell/platform/fuchsia/flutter:aot" + platform_name = "flutter_runner" + is_aot = true + is_product = false + enable_asserts = false +} + +# Builds the component in a product AOT build. This will +# not launch the vm service in the runner. +flutter_release_build_cfg = { + runtime_meta = + "//flutter/shell/platform/fuchsia/flutter/meta/aot_product_runtime" + runner_dep = "//flutter/shell/platform/fuchsia/flutter:aot_product" + platform_name = "flutter_runner" + is_aot = true + is_product = true + enable_asserts = false +} diff --git a/engine/src/flutter/tools/fuchsia/flutter/flutter_component.gni b/engine/src/flutter/tools/fuchsia/flutter/flutter_component.gni new file mode 100644 index 0000000000..3e1ffb04b9 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/flutter/flutter_component.gni @@ -0,0 +1,137 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/dart/dart.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/flutter/config.gni") +import("//flutter/tools/fuchsia/flutter/internal/flutter_dart_component.gni") + +# Defines a Flutter component which can be used in a fuchsia package +# +# Flutter components require at least one library which contains a main +# entry point. The library should be defined using the dart_library.gni. +# +# ``` +# dart_library("lib") { +# package_name = "my_library" +# sources = [ "main.dart" ] +# deps = [ "//third_party/dart-pkg/git/flutter/packages/flutter" ] +# } +# ``` +# +# Once a library is defined a flutter component can be created which +# depends on this package. If the component needs any other resources they may +# be defined using the resource target and added to the components deps. +# +# ``` +# resource("text-file") { +# sources = [ "text_file.txt" ] +# outputs = [ "data/text_file.txt" ] +# } +# +# flutter_component("my-component") { +# manifest = "meta/my-component.cmx" +# main_package = "my_library" +# deps = [ +# ":lib", +# ":text-file", +# ] +# } +# ``` +# +# Once a component is defined it can be added as a dep of a fuchsia_package +# ``` +# fuchsia_package("my-package") { +# deps = [ +# ":my-component", +# ] +# } +# ``` +# +# Parameters +# +# manifest (required) +# The component manifest +# Type: path +# +# main_package (optional) +# The name of the package containing main_dart +# Type: string +# Default: component_name with dashes replaced by underscores, if defined. +# Otherwise, the target_name with dashes replaced by underscores will be +# used. +# +# component_name (optional) +# The name of the component. +# Type: string +# Default: target_name +# +# main_dart (optional) +# File containing the main function of the component. +# Type: string +# Default: main.dart +# +# package_root (optional) +# The root of the package generated for this component. Each component must +# have a unique package_root. For each component, there must be a +# pubspec.yaml and an analysis_options.yaml at the package root. +# Type: path +# Default: "." +# +# build_cfg (optional) +# Specifies the parameters for building the component. +# See //flutter/tools/fuchsia/flutter/flutter_build_config.gni for predefined configs. +# +# deps +# testonly +# visibility +template("flutter_component") { + assert(defined(invoker.manifest), "Must define manifest") + + _component_deps = [] + if (defined(invoker.deps)) { + _component_deps += invoker.deps + } + + if (defined(invoker.build_cfg)) { + _build_cfg = invoker.build_cfg + } else { + _build_cfg = flutter_default_build_cfg + } + + if (defined(invoker.main_package)) { + _main_package = invoker.main_package + } else if (defined(invoker.component_name)) { + _main_package = string_replace(invoker.component_name, "-", "_") + } else { + _main_package = string_replace(target_name, "-", "_") + } + + if (defined(invoker.main_dart)) { + _main_dart = invoker.main_dart + } else { + _main_dart = "main.dart" + } + + # We need to specify the runner as a dependency to be bundled with the dill + # files generated by dart_kernel. + _component_deps += [ _build_cfg.runner_dep ] + + flutter_dart_component(target_name) { + forward_variables_from(invoker, + "*", + [ + "build_cfg", + "deps", + "main_dart", + "main_package", + ]) + deps = _component_deps + + main_dart = _main_dart + main_package = _main_package + + build_cfg = _build_cfg + } +} diff --git a/engine/src/flutter/tools/fuchsia/flutter/internal/flutter_dart_component.gni b/engine/src/flutter/tools/fuchsia/flutter/internal/flutter_dart_component.gni new file mode 100644 index 0000000000..411510f81f --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/flutter/internal/flutter_dart_component.gni @@ -0,0 +1,244 @@ +# Copyright 2013 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("//flutter/tools/executable_action.gni") +import("//flutter/tools/fuchsia/dart/config.gni") +import("//flutter/tools/fuchsia/dart/dart.gni") +import("//flutter/tools/fuchsia/dart/dart_package_config.gni") +import("//flutter/tools/fuchsia/dart/kernel/dart_kernel.gni") +import("//flutter/tools/fuchsia/dist/resource.gni") +import("//flutter/tools/fuchsia/sdk/cmc.gni") +import("//flutter/tools/fuchsia/sdk/component.gni") + +# Defines a component which will run in a flutter_runner or dart_runner +# +# This template is not intended to be used directly. Users should use the +# flutter_component and dart_component actions instead. +# +# Parameters +# +# manifest (required) +# The component manifest +# Type: path +# +# main_package (required) +# The name of the package containing the main_dart +# Type: string +# +# component_name (optional) +# The name of the component. +# Type: string +# Default: target_name +# +# build_cfg (required) +# A description of how to build this component. This object needs +# to contain the following variables: +# runtime_meta: a path to the partial cmx file containing the runner +# platform_name: either 'dart_runner' or 'flutter_runner' +# is_aot: a boolean indicating if this is an AOT build +# is_product: a boolean indicating if this is a product build +# enable_asserts: whether we should enable asserts when compiling +# +# main_dart (required) +# File containing the main function of the component. +# Type: string +# +# generate_asset_manifest (optional) +# If true, will generate an asset manifest for this component +# Type: boolean +# Default: false +# +# deps +# testonly +# visibility +template("flutter_dart_component") { + assert(defined(invoker.manifest), "must specify a manifest file") + assert(defined(invoker.build_cfg), "must specify build_cfg") + assert(defined(invoker.main_dart), "Must specify main_dart") + assert(defined(invoker.main_package), "Must specify main_package") + + build_cfg = invoker.build_cfg + + _component_deps = [] + if (defined(invoker.deps)) { + _component_deps += invoker.deps + } + + if (defined(invoker.component_name)) { + _component_name = invoker.component_name + } else { + _component_name = target_name + } + + # merge the runner cmx into this one which allows us to switch runners based + # on compilation modes + _merged_target_name = "${target_name}_merged.cmx" + + cmc_merge(_merged_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + sources = [ + invoker.manifest, + rebase_path(build_cfg.runtime_meta, "."), + ] + } + + _merged_outputs = [] + _merged_outputs += get_target_outputs(":$_merged_target_name") + _merged_manifest = _merged_outputs[0] + + _component_deps += [ ":$_merged_target_name" ] + + _dart_package_config_target_name = "${target_name}_dart_package" + dart_package_config(_dart_package_config_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + + deps = _component_deps + } + + _package_config_output = [] + _package_config_output = + get_target_outputs(":$_dart_package_config_target_name") + _packages_path = _package_config_output[0] + + _kernel_target_name = _component_name + "_kernel" + _kernel_target_dep_name = _kernel_target_name + "_gen_file" + _kernel_path = "$target_gen_dir/__untraced_dart_kernel__/${target_name}.dil" + + dart_kernel(_kernel_target_name) { + kernel_path = _kernel_path + + # establishes a dependency chain for the snapshot since + # the kernel is wrapped in a group + kernel_target_name = _kernel_target_dep_name + + forward_variables_from(invoker, + [ + "testonly", + "visibility", + "main_dart", + "main_package", + ]) + + deps = [ ":$_dart_package_config_target_name" ] + + packages_path = _packages_path + args = [ + "--component-name", + _component_name, + ] + + # always generate a manifest for fuchsia builds. If this is an aot build + # the kernel will ignore this variable. + generate_manifest = true + + platform_name = build_cfg.platform_name + product = build_cfg.is_product + is_aot = build_cfg.is_aot + } + + _component_deps += [ ":$_kernel_target_name" ] + + if (build_cfg.is_aot) { + _snapshot_path = "$target_gen_dir/${_component_name}_snapshot.so" + _snapshot_target_name = target_name + "_snapshot" + + _stats_json_path = + "$target_gen_dir/${_component_name}/stats/symbol_sizes.json" + + if (build_cfg.is_product) { + _gen_snapshot_script_target = gen_snapshot_product + } else { + _gen_snapshot_script_target = gen_snapshot + } + + executable_action(_snapshot_target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + + deps = [ ":$_kernel_target_dep_name" ] + inputs = [ _kernel_path ] + outputs = [ + _snapshot_path, + _stats_json_path, + ] + + if (defined(invoker.toolchain)) { + toolchain = invoker.toolchain + } else { + toolchain = host_toolchain + } + + # Construct the host toolchain version of the tool. + # host_tool = invoker.tool + "($toolchain)" + host_tool = _gen_snapshot_script_target + "($toolchain)" + + # Get the path to the executable. Currently, this assumes that the tool + # does not specify output_name so that the target name is the name to use. + # If that's not the case, we'll need another argument to the script to + # specify this, since we can't know what the output name is (it might be in + # another file not processed yet). + host_executable = + get_label_info(host_tool, "root_out_dir") + "/" + + get_label_info(host_tool, "name") + host_executable_suffix + + # Add the executable itself as an input. + inputs += [ host_executable ] + + deps += [ host_tool ] + + tool = host_executable + + args = [ + "--deterministic", + "--snapshot_kind=app-aot-elf", + "--elf=" + rebase_path(_snapshot_path, root_build_dir), + "--print-instructions-sizes-to=" + + rebase_path(_stats_json_path, root_build_dir), + ] + + # No asserts in debug or release product. + # No asserts in non-product release + # Yes asserts in non-product debug. + # if (is_debug && !dart_force_product) + if (build_cfg.enable_asserts) { + args += [ "--enable_asserts" ] + } + args += [ rebase_path(_kernel_path, root_build_dir) ] + } + + # copy the snapshot as a resource + _snapshot_resource_target_name = "${target_name}_snapshot_resource" + resource(_snapshot_resource_target_name) { + sources = [ _snapshot_path ] + outputs = [ "data/${_component_name}/app_aot_snapshot.so" ] + } + + _component_deps += [ + ":$_snapshot_resource_target_name", + ":$_snapshot_target_name", + ] + } + + fuchsia_component(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + "component_name", + ]) + deps = _component_deps + manifest = _merged_manifest + } +} diff --git a/engine/src/flutter/tools/fuchsia/make_build_info.py b/engine/src/flutter/tools/fuchsia/make_build_info.py old mode 100644 new mode 100755 diff --git a/engine/src/flutter/tools/fuchsia/python/package_python_binary.py b/engine/src/flutter/tools/fuchsia/python/package_python_binary.py new file mode 100755 index 0000000000..5cda28b74c --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/python/package_python_binary.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3.8 +"""Creats a Python zip archive for the input main source.""" + +# Copyright 2013 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 argparse +import json +import os +import shutil +import sys +import zipapp + + +def main(): + parser = argparse.ArgumentParser( + 'Creates a Python zip archive for the input main source') + + parser.add_argument( + '--target_name', + help='Name of the build target', + required=True, + ) + + parser.add_argument( + '--main_source', + help='Path to the source containing the main function', + required=True, + ) + parser.add_argument( + '--main_callable', + help= + 'Name of the the main callable, that is the entry point of the generated archive', + required=True, + ) + + parser.add_argument( + '--gen_dir', + help='Path to gen directory, used to stage temporary directories', + required=True, + ) + parser.add_argument('--output', help='Path to output', required=True) + + parser.add_argument( + '--sources', + help='Sources of this target, including main source', + nargs='*', + ) + parser.add_argument( + '--library_infos', + help='Path to the library infos JSON file', + type=argparse.FileType('r'), + required=True, + ) + parser.add_argument( + '--depfile', + help='Path to the depfile to generate', + type=argparse.FileType('w'), + required=True, + ) + + args = parser.parse_args() + + infos = json.load(args.library_infos) + + # Temporary directory to stage the source tree for this python binary, + # including sources of itself and all the libraries it imports. + # + # It is possible to have multiple python_binaries in the same directory, so + # using target name, which should be unique in the same directory, to + # distinguish between them. + app_dir = os.path.join(args.gen_dir, args.target_name) + os.makedirs(app_dir, exist_ok=True) + + # Copy over the sources of this binary. + for source in args.sources: + basename = os.path.basename(source) + if basename == '__main__.py': + print( + '__main__.py in sources of python_binary is not supported, see https://fxbug.dev/73576', + file=sys.stderr, + ) + return 1 + dest = os.path.join(app_dir, basename) + shutil.copy2(source, dest) + + # For writing a depfile. + files_to_copy = [] + # Make sub directories for all libraries and copy over their sources. + for info in infos: + dest_lib_root = os.path.join(app_dir, info['library_name']) + os.makedirs(dest_lib_root, exist_ok=True) + + src_lib_root = info['source_root'] + # Sources are relative to library root. + for source in info['sources']: + src = os.path.join(src_lib_root, source) + dest = os.path.join(dest_lib_root, source) + # Make sub directories if necessary. + os.makedirs(os.path.dirname(dest), exist_ok=True) + files_to_copy.append(src) + shutil.copy2(src, dest) + + args.depfile.write('{}: {}\n'.format(args.output, ' '.join(files_to_copy))) + + # Main module is the main source without its extension. + main_module = os.path.splitext(os.path.basename(args.main_source))[0] + # Manually create a __main__.py file for the archive, instead of using the + # `main` parameter from `create_archive`. This way we can import everything + # from the main module (create_archive only `import pkg`), which is + # necessary for including all test cases for unit tests. + # + # TODO(https://fxbug.dev/73576): figure out another way to support unit + # tests when users need to provide their own custom __main__.py. + main_file = os.path.join(app_dir, "__main__.py") + with open(main_file, 'w') as f: + f.write( + f''' +import sys +from {main_module} import * + +sys.exit({args.main_callable}()) +''') + + zipapp.create_archive( + app_dir, + target=args.output, + interpreter='/usr/bin/env python3.8', + compressed=True, + ) + + # Manually remove the temporary app directory and all the files, instead of + # using shutil.rmtree. rmtree records reads on directories which throws off + # the action tracer. + for root, dirs, files in os.walk(app_dir, topdown=False): + for file in files: + os.remove(os.path.join(root, file)) + for dir in dirs: + os.rmdir(os.path.join(root, dir)) + os.rmdir(app_dir) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/python/python_binary.gni b/engine/src/flutter/tools/fuchsia/python/python_binary.gni new file mode 100644 index 0000000000..f5d7930dea --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/python/python_binary.gni @@ -0,0 +1,123 @@ +# Copyright 2013 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("//flutter/tools/python/python3_action.gni") + +# Defines a Python binary. +# +# Example +# +# ``` +# python_binary("main") { +# main_source = "main.py" +# main_callable = "main" +# sources = [ +# "foo.py", +# "bar.py", +# ] +# output_name = "main.pyz" +# deps = [ "//path/to/lib" ] +# } +# ``` +# +# Parameters +# +# main_source (required) +# Source file including the entry callable for this binary. +# This file will typically contain +# ``` +# if __name__ == "__main__": +# main() +# ``` +# Type: path +# +# main_callable (optional) +# Main callable, which serves as the entry point of the output zip archive. +# In the example above, this is "main". +# Type: string +# Default: main +# +# output_name (optional) +# Name of the output Python zip archive, must have .pyz as extension. +# Type: string +# Default: ${target_name}.pyz +# +# sources +# deps +# visibility +# testonly +template("python_binary") { + assert(defined(invoker.main_source), "main_source is required") + + _library_infos_target = "${target_name}_library_infos" + _library_infos_json = "${target_gen_dir}/${target_name}_library_infos.json" + generated_file(_library_infos_target) { + forward_variables_from(invoker, + [ + "testonly", + "deps", + ]) + visibility = [ ":*" ] + + outputs = [ _library_infos_json ] + output_conversion = "json" + data_keys = [ "library_info" ] + walk_keys = [ "library_info_barrier" ] + } + + # action(target_name) { + python3_action(target_name) { + forward_variables_from(invoker, + [ + "testonly", + "visibility", + ]) + + sources = [ invoker.main_source ] + if (defined(invoker.sources)) { + sources += invoker.sources + } + inputs = [ _library_infos_json ] + deps = [ ":${_library_infos_target}" ] + + # Output must be a .pyz, so our build knows to use a vendored Python + # interpreter to run them. + # + # Output a single .pyz file makes the output deterministic, otherwise we'd + # have to list out all the library sources that will be copied to output + # directory, which is not possible because they are not known until the + # generated JSON file is parsed at build time. + _output = "${target_out_dir}/${target_name}.pyz" + if (defined(invoker.output_name)) { + assert(get_path_info(invoker.output_name, "extension") == "pyz", + "output_name must have .pyz as extension") + _output = "${target_out_dir}/${invoker.output_name}" + } + outputs = [ _output ] + + _main_callable = "main" + if (defined(invoker.main_callable)) { + _main_callable = invoker.main_callable + } + + script = "//flutter/tools/fuchsia/python/package_python_binary.py" + depfile = "${target_out_dir}/${target_name}.d" + args = [ "--sources" ] + rebase_path(sources, root_build_dir) + [ + "--target_name", + target_name, + "--main_source", + rebase_path(invoker.main_source, root_build_dir), + "--main_callable", + _main_callable, + "--library_infos", + rebase_path(_library_infos_json, root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + "--gen_dir", + rebase_path(target_gen_dir, root_build_dir), + "--output", + rebase_path(_output, root_build_dir), + ] + } +} diff --git a/engine/src/flutter/tools/fuchsia/sdk/cmc.gni b/engine/src/flutter/tools/fuchsia/sdk/cmc.gni new file mode 100644 index 0000000000..fce17f4786 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/sdk/cmc.gni @@ -0,0 +1,243 @@ +# Copyright 2013 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("config/config.gni") + +# Internal template for the cmc tool. +# +# Invokes cmc +# +# Example: +# +# ``` +# _cmc_tool("validate_cmx") { +# inputs = [ manifest ] +# outputs = [ stamp_file ] +# +# args = [ +# "--stamp", +# rebase_path(stamp_file, root_build_dir), +# "validate", +# rebase_path(invoker.manifest), +# ] +# } +# ``` +# +# Parameters: +# +# inputs (required) +# List of files that are input for cmc. +# Type: list(path) +# +# outputs (required) +# List paths that are output for the run of cmc. +# Type: list(path) +# +# args (required) +# List command line args for cmc. +# Type: list(path) +# +# deps +# public_deps +# testonly +# visibility +# +template("_cmc_tool") { + action(target_name) { + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "testonly", + "visibility", + ]) + script = "//build/gn_run_binary.py" + _cmc_tool_path = "${fuchsia_tool_dir}/cmc" + + assert(defined(invoker.inputs), "inputs is a required parameter.") + assert(defined(invoker.outputs), "outputs is a required parameter.") + assert(defined(invoker.args), "args is a required parameter.") + + inputs = + [ + _cmc_tool_path, + + # Depend on the SDK hash, to ensure rebuild if the SDK tools change. + fuchsia_sdk_manifest_file, + ] + invoker.inputs + + outputs = invoker.outputs + + args = [ rebase_path(_cmc_tool_path, root_build_dir) ] + invoker.args + } +} + +# Compiles a Components Framework v2 manifest (.cml) file to .cm +# +# Example: +# +# ``` +# cmc_compile(_compiled_manifest_target) { +# forward_variables_from(invoker, [ "deps" ]) +# manifest = rebase_path(manifest) +# } +# ``` +# +# Parameters: +# +# manifest (required) +# The input Component Framework v2 manifest source (.cml) file. +# The file must have the extension ".cml". +# +# output_name (optional) +# Name of the output file to generate. Defaults to $target_name. +# This should not include a file extension (.cm) +# +# deps +# public_deps +# testonly +# visibility +# +template("cmc_compile") { + output_name = target_name + if (defined(invoker.output_name)) { + output_name = invoker.output_name + } + + _cmc_tool(target_name) { + forward_variables_from(invoker, + [ + "deps", + "manifest", + "public_deps", + "testonly", + "visibility", + ]) + assert(defined(manifest), "manifest file required") + + inputs = [ manifest ] + + output_file = "$target_out_dir/$output_name.cm" + outputs = [ output_file ] + + depfile = "$target_out_dir/$target_name.d" + + args = [ + "compile", + rebase_path(manifest, root_build_dir), + "--output", + rebase_path(output_file, root_build_dir), + "--includeroot", + rebase_path("//", root_build_dir), + "--includepath", + rebase_path("$fuchsia_sdk/pkg/", root_build_dir), + "--depfile", + rebase_path(depfile, root_build_dir), + ] + } +} + +# Validates a component manifest file +# +# The cmc_validate template will ensure that a given cmx file is conformant to +# the cmx schema, as defined by //tools/cmc/schema.json. A stamp file is +# generated to mark that a given cmx file has passed. +# +# Parameters: +# +# manifest (required) +# [file] The path to the cmx file that is to be validated +# +# deps +# testonly +# visibility +# +template("cmc_validate") { + _cmc_tool(target_name) { + forward_variables_from(invoker, + [ + "manifest", + "deps", + "testonly", + "visibility", + ]) + stamp_file = "$target_gen_dir/$target_name.verified" + + assert(defined(manifest), "manifest file required") + + inputs = [ manifest ] + + outputs = [ stamp_file ] + + args = [ + "--stamp", + rebase_path(stamp_file, root_build_dir), + "validate", + rebase_path(invoker.manifest), + ] + } +} + +# Merges multiple component manifest files into one. +# +# Combines mutliple component manifests into a single manifest. +# This is useful for merging fragments of sandbox configurations into +# a single component manifest. +# +# Example +# +# ``` +# cmc_merge("combined_cmx") { +# sources = ["sandbox.cmx", "services.cmx", "program.cmx"] +# output_name = "my-component.cmx" +# } +# ``` +# +# Parameters +# +# sources +# The list of cmx files to merge together. +# +# Type: list of strings (filepath) +# +# output_name [optional] +# The name of the merged cmx file. This file is created in $target_out_dir. +# If not specified, $target_name.cmx is used. +# +# Type: string +# +# Standard parameters: +# deps +# testonly +# visibility +# +template("cmc_merge") { + _cmc_tool(target_name) { + forward_variables_from(invoker, + [ + "deps", + "output_name", + "sources", + "testonly", + "visibility", + ]) + if (!defined(output_name)) { + output_name = "${target_name}.cmx" + } + + merged_output = "${target_out_dir}/${output_name}" + inputs = invoker.sources + outputs = [ merged_output ] + + args = [ + "merge", + "--output", + rebase_path(merged_output, root_build_dir), + ] + + foreach(source, sources) { + args += [ rebase_path(source, root_build_dir) ] + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/sdk/component.gni b/engine/src/flutter/tools/fuchsia/sdk/component.gni new file mode 100644 index 0000000000..5d85ff0553 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/sdk/component.gni @@ -0,0 +1,233 @@ +# Copyright 2013 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("cmc.gni") + +declare_args() { + # Enable code coverage for Fuchsia components. Only applies to v1 components. + fuchsia_code_coverage = false +} + +# Defines a Fuchsia component. +# See: https://fuchsia.dev/fuchsia-src/glossary#component +# +# A component is defined by a component manifest. +# A component is a unit of executable software on Fuchsia. When the componet is executed, there are usually many files +# needed at runtime, such as the executable itself, shared libraries, and possibly other data resources. These files are +# specified by using the GN concept of "data_deps". +# +# Components are distributed using a fuchsia package which can contain multiple components. The component manifest and +# the runtime dependencies of the component are packaged using the fuchsia_package rule. +# +# By default, the runtime depenencies of the component are included in the package using relative paths calcualted from +# the "closest" root of: $root_gen_dir, $root_dir, $out_dir. If a specific path is desired in the package, the "resources" parameter can +# be used to explicitly specify the path of a specific file. +# +# A component is launched by a URL which identifies the package and the component manifest. +# See: https://fuchsia.dev/fuchsia-src/glossary#component_url +# +# Example +# +# ``` +# fuchsia_component("my_component") { +# manifest = "meta/component-manifest.cmx" +# resources = [ +# path = "testdata/use_case1.json" +# dest = "data/testdata/use_case1.json" +# ] +# data_deps = [ ":hello_world_executable"] +# } +# ``` +# +# Parameters +# +# manifest +# The source manifest file for this component. This can be either v1 (.cmx) +# or v2 (.cml) manifest. +# +# Type: string (filepath) +# +# data_deps +# The labels of targets that generate the files needed at runtime by the component. +# At minimum, this should include the label of the binary to include in the component. +# +# Type: list of labels +# +# manifest_output_name [optional] +# The name of the manifest file contained in the distribution that this +# component is included in. The output name should have no extension or +# prefixes. The resulting filename will have the extension correct for the +# manifest version. For example, if `manifest` is "foo.cmx" +# and `manifest_output_name` is "bar", the filename will be "bar.cmx". If +# `manifest` is "foo.cml" (a v2 manifest), the filename will be "bar.cm". +# +# Type: string +# Default: The base file name of `manifest` without an extension. +# +# resources [optional] +# Lists additional files to include for runtime access by the component. +# Defines the resources in a package containing this component. A resource +# is a data file that may be produced by the build system, checked in to a +# source repository, or produced by another system that runs before the +# build. Resources are placed in the `data/` directory of the assembled +# package. +# +# Type: list of scopes. Entries in a scope in the resources list: +# path (required) +# Location of resource in source or build directory. If the +# resource is checked in, this will typically be specified +# as a path relative to the BUILD.gn file containing the +# `package()` target. If the resource is generated, this will +# typically be specified relative to `$target_gen_dir`. +# +# dest (required) string (path) Location the resource will be placed within `data/`. +# +# Standard parameters: +# deps +# testonly +# visibility +# +# Metadata +# contents - list of scopes describing files for this component. This metadata is consumed by +# The fuchsia_package rule, which describes the entries used. +# Entries in scope: +# type: the usage type of the element, manifest or resource. +# Each type can have specific properties included in the scope. +# source [type == manifest || resource] The source file to include in +# the package for this component. In the case of v2 components, +# this is the compiled manifest. +# output_name [type == manifest] The basename of the manifest as it should appear in the package. +# manifest_version [type == manifest] +# dest: [type == resource] The package relative path of the resource. + +template("fuchsia_component") { + forward_variables_from(invoker, + [ + "manifest", + "manifest_output_name", + ]) + + assert(defined(manifest), "manifest file required for this component") + + if (!defined(manifest_output_name)) { + manifest_output_name = get_path_info(manifest, "name") + } + + # Determine manifest_version from the `manifest` file extension. + _manifest_extension = get_path_info(manifest, "extension") + assert(_manifest_extension == "cmx" || _manifest_extension == "cml", + "Manifest file extension must be .cmx or .cml") + if (_manifest_extension == "cmx") { + _manifest_version = "v1" + } else if (_manifest_extension == "cml") { + _manifest_version = "v2" + } + + if (fuchsia_code_coverage) { + if (_manifest_version == "v1") { + merged_manifest = "${target_name}-coverage.cmx" + cmc_merge(merged_manifest) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + sources = [ + "${fuchsia_sdk}/build/enable_coverage_data.cmx", + manifest, + ] + output_name = merged_manifest + } + manifest = "${target_out_dir}/${merged_manifest}" + } + } + + # The component manifest validated using cmx_validation for v1, + # or cmc_compile for v2. + _cm_validation_target = + "${target_name}_validate_" + get_path_info(manifest, "file") + + if (_manifest_version == "v1") { + cmc_validate(_cm_validation_target) { + forward_variables_from(invoker, [ "testonly" ]) + manifest = manifest + deps = [] + if (defined(invoker.deps)) { + deps += invoker.deps + } + if (fuchsia_code_coverage) { + deps += [ ":${merged_manifest}" ] + } + } + + _manifest_source = manifest + } else if (_manifest_version == "v2") { + cmc_compile(_cm_validation_target) { + forward_variables_from(invoker, + [ + "deps", + "testonly", + ]) + manifest = rebase_path(manifest) + } + + _compiled_manifest_outputs = get_target_outputs(":$_cm_validation_target") + _manifest_source = _compiled_manifest_outputs[0] + } + + group(target_name) { + forward_variables_from(invoker, + [ + "data", + "deps", + "data_deps", + "resources", + "testonly", + "visibility", + ]) + + # Data is used for adding files as runtime dependencies. Files used as + # sources in the resources parameter are added as data to make sure + # non-generated files listed are captured as dependencies. + if (!defined(data)) { + data = [] + } + + if (!defined(resources)) { + resources = [] + } + + if (!defined(deps)) { + deps = [] + } + deps += [ ":$_cm_validation_target" ] + + # Create the component metadata entries. These capture the manifest information and + # the resources parameter contents. The metadata is intended for the fuchsia_package rule. + component_contents = [ + { + type = "manifest" + source = rebase_path(_manifest_source) + output_name = manifest_output_name + manifest_version = _manifest_version + }, + ] + foreach(resource, resources) { + data += [ resource.path ] + component_contents += [ + { + type = "resource" + source = rebase_path(resource.path) + dest = resource.dest + }, + ] + } + + # The contents of the component are enumerated in the + # metadata. + metadata = { + contents = [ component_contents ] + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/sdk/config/config.gni b/engine/src/flutter/tools/fuchsia/sdk/config/config.gni new file mode 100644 index 0000000000..4e4ecc0617 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/sdk/config/config.gni @@ -0,0 +1,39 @@ +# Copyright 2013 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("//build/fuchsia/sdk.gni") + +declare_args() { + # Path to the fuchsia SDK. This is intended for use in other templates & rules + # to reference the contents of the fuchsia SDK. + fuchsia_sdk = "$fuchsia_sdk_path" + + # Build ID uniquely identifying the Fuchsia IDK. This is exposed as a property so + # it can be used to locate images and packages on GCS and as a marker to indicate the + # "version" of the IDK. If it is empty, then it is most likely that something is fatally wrong. + fuchsia_sdk_id = "" +} + +declare_args() { + # The SDK manifest file. This is useful to include as a dependency + # for some targets in order to cause a rebuild when the version of the + # SDK is changed. + fuchsia_sdk_manifest_file = "$fuchsia_sdk/meta/manifest.json" + + # fuchsia_tool_dir is use to specify the directory in the SDK to locate tools for the + # host cpu architecture. If the host_cpu is not recognized, then tool dir + # defaults to x64. + fuchsia_tool_dir = "$fuchsia_sdk/tools/x64" + if (host_cpu == "x64" || host_cpu == "arm64") { + fuchsia_tool_dir = "${fuchsia_sdk}/tools/${host_cpu}" + } +} + +if (fuchsia_sdk_id == "") { + # Note: If we need to expose more than just the id in the future, + # we should consider exposing the entire json object for the metadata vs. + # adding a bunch of variables. + _meta = read_file(fuchsia_sdk_manifest_file, "json") + fuchsia_sdk_id = _meta.id +} diff --git a/engine/src/flutter/tools/fuchsia/sdk/find_dart_libraries.py b/engine/src/flutter/tools/fuchsia/sdk/find_dart_libraries.py new file mode 100755 index 0000000000..1d3c0b2918 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/sdk/find_dart_libraries.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright 2013 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. + +""" Resolve a dart third_party library dependency by returning the path to an +existing downloaded version one of a few possible subdirectories under +//third_party. Some of these pre-downloaded libraries may be directly downloaded +via a //flutter/DEPS entry. Others may be bundled with the Dart SDK, in one of +its provided libraries or its third_party packages. +""" + +import argparse +import json +import yaml +import os +import sys +import pkg_resources + + +def find_package(root, local_paths, package, version): + """Return the package target if found with at least this version""" + needed_version = pkg_resources.parse_version(version) + for local_path in local_paths: + package_path = os.path.join(local_path, package) + pubspec_yaml = os.path.join(root, package_path, 'pubspec.yaml') + if os.path.exists(pubspec_yaml): + with open(pubspec_yaml) as yaml_file: + pubspec = yaml.safe_load(yaml_file) + found_version = pkg_resources.parse_version(pubspec['version']) + if found_version >= needed_version: + return "//" + package_path + return None + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '--ignore-missing', + action='store_true', + help=('Return a success exit status, with all matched dart libraries, ' + 'even if one or more matches were not found')) + parser.add_argument( + '--root', + help='Path to the flutter engine src root', + required=True) + parser.add_argument( + '--target', + help='The name of the target that consolidates the derived deps', + required=True) + parser.add_argument( + '--local-path', + help=('A parent directory of pre-existing third_party dart libraries ' + '(multiple --local-path arguments allowed)'), + action='append', + dest='local_paths', + required=True) + + (args, versioned_dart_packages) = parser.parse_known_args() + + assert os.path.exists(args.root) + + for path in args.local_paths: + assert os.path.isdir(os.path.join(args.root, path)) + + if len(versioned_dart_packages) > 0: + assert len(versioned_dart_packages) % 2 == 0, ( + 'Each third_party_dep package must be accompanied by a version') + + dart_package_paths = [] + it = iter(versioned_dart_packages) + for item in it: + (package, version) = item, next(it) + path = find_package(args.root, args.local_paths, package, version) + if path is not None: + dart_package_paths.append(path) + else: + print( + 'No package found for %s with version %s' % (package, version), + file=sys.stderr) + if not args.ignore_missing: + return 1 + + json.dump(dart_package_paths, sys.stdout) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/engine/src/flutter/tools/fuchsia/sdk/sdk_targets.gni b/engine/src/flutter/tools/fuchsia/sdk/sdk_targets.gni new file mode 100644 index 0000000000..dd6f137c8f --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/sdk/sdk_targets.gni @@ -0,0 +1,139 @@ +# Copyright 2013 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/fidl/fidl.gni") + +_fuchsia_sdk_path = "//fuchsia/sdk/$host_os" + +template("_fuchsia_dart_library") { + assert(defined(invoker.meta), "The meta file content must be specified.") + meta = invoker.meta + assert(meta.type == "dart_library") + + _output_name = meta.name + _sources = [] + _deps = [] + + if (defined(meta.dart_library_null_safe) && meta.dart_library_null_safe) { + _dart_language_version = "2.12" + } else { + _dart_language_version = "2.0" + } + + foreach(source, meta.sources) { + _sources += [ string_replace("$source", "${meta.root}/lib/", "") ] + } + + foreach(dep, meta.deps) { + _deps += [ "../dart:$dep" ] + } + + foreach(dep, meta.fidl_deps) { + _deps += [ "//flutter/tools/fuchsia/fidl:$dep" ] + } + + # Compute the third_party deps. The Fuchsia SDK doesn't know where to resolve + # a third_party dep, and Flutter has third_party Dart libraries in more than + # one location (some downloaded directly, others as part of the Dart SDK or + # bundled as a Dart SDK third_party dep). Find a matching library. If a match + # is required by a build dependency, and a match is not found, you may need to + # add an entry for it in `flutter/DEPS`. + known_pkg_paths = [ + "third_party/pkg", + "third_party/dart/pkg", + "third_party/dart/third_party/pkg", + ] + + args = [ + "--ignore-missing", + "--root", + rebase_path("//"), + "--target", + rebase_path(target_name, "//"), + ] + + foreach(path, known_pkg_paths) { + args += [ + "--local-path", + path, + ] + } + + foreach(third_party_dep, meta.third_party_deps) { + name = third_party_dep.name + version = third_party_dep.version + args += [ + name, + version, + ] + } + + third_party_deps_json = + exec_script("//flutter/tools/fuchsia/sdk/find_dart_libraries.py", + args, + "json") + + _deps += third_party_deps_json + + dart_library(target_name) { + package_root = "${_fuchsia_sdk_path}/${meta.root}" + package_name = _output_name + language_version = _dart_language_version + sources = _sources + deps = _deps + } +} + +template("_fuchsia_fidl_library") { + assert(defined(invoker.meta), "The meta file content must be specified.") + meta = invoker.meta + assert(meta.type == "fidl_library") + + fidl(target_name) { + sources = [] + foreach(source, meta.sources) { + sources += [ "$fuchsia_sdk_path/$source" ] + } + public_deps = [] + foreach(dep, meta.deps) { + public_deps += [ ":$dep" ] + } + } +} + +template("sdk_targets") { + assert(defined(invoker.meta), "The meta.json file path must be specified.") + + target_type = invoker.target_name + + meta_json = read_file(invoker.meta, "json") + + foreach(part, meta_json.parts) { + part_meta_json = { + } + + part_meta = part.meta + part_meta_rebased = "$fuchsia_sdk_path/$part_meta" + + part_meta_json = read_file(part_meta_rebased, "json") + subtarget_name = part_meta_json.name + + if (part.type == target_type) { + if (target_type == "dart_library") { + _fuchsia_dart_library(subtarget_name) { + meta = part_meta_json + } + } else if (target_type == "fidl_library") { + _fuchsia_fidl_library(subtarget_name) { + meta = part_meta_json + } + } + } + } + + group(target_name) { + } +} diff --git a/engine/src/flutter/tools/fuchsia/toolchain/basic_toolchain.gni b/engine/src/flutter/tools/fuchsia/toolchain/basic_toolchain.gni new file mode 100644 index 0000000000..5dd4636b28 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/toolchain/basic_toolchain.gni @@ -0,0 +1,58 @@ +# Copyright 2013 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("//flutter/tools/fuchsia/toolchain/default_tools.gni") + +declare_args() { + toolchain_variant = { + } +} + +# Define a basic GN toolchain() instance that only has the "stamp" and "copy" +# tools. Use this for toolchains that will only have action() targets. +# +# Parameters: +# expected_label: Optional +# If provided, this template will assert that the full toolchain label +# matches this value. This is useful when the definition should match +# a global variable (e.g. go_toolchain or dart_toolchain). +# Type: GN label +# +# toolchain_args: Optional +# A scope of extra toolchain_args keys for this toolchain instance. +# +# NOTE: This also ensures this defines the global `toolchain_variant.base` +# variable, allowing BUILDCONFIG.gn to detect that this is not the default +# toolchain (see the definition of in_default_toolchain in that file for +# more details). +template("basic_toolchain") { + # The line below is required, otherwise 'gn gen' will complain with an error + # (i.e. 'You set the variable "invoker" here and it was unused before it went + # out of scope') if the template is called without any arguments. + not_needed([ "invoker" ]) + + toolchain(target_name) { + tool("stamp") { + command = stamp_command + description = stamp_description + } + tool("copy") { + command = copy_command + description = copy_description + } + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + toolchain_variant = { + base = get_label_info(":$target_name", "label_no_toolchain") + if (defined(invoker.expected_label)) { + assert( + base == invoker.expected_label, + "Invalid toolchain label $base, expected ${invoker.expected_label}") + } + } + } + } +} diff --git a/engine/src/flutter/tools/fuchsia/toolchain/copy.py b/engine/src/flutter/tools/fuchsia/toolchain/copy.py new file mode 100755 index 0000000000..e4c992de50 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/toolchain/copy.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3.8 +# Copyright 2013 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. +"""Emulation of `rm -f out && cp -af` in out. This is necessary on Mac in order +to preserve nanoseconds of mtime. See https://fxbug.dev/56376#c5.""" + +import os +import shutil +import sys + + +def main(): + if len(sys.argv) != 3: + print('usage: copy.py source dest', file=sys.stderr) + return 1 + source = sys.argv[1] + dest = sys.argv[2] + + if os.path.isdir(source): + print( + f'{source} is a directory, tool "copy" does not support directory copies' + ) + return 1 + + if os.path.exists(dest): + if os.path.isdir(dest): + + def _on_error(fn, path, dummy_excinfo): + # The operation failed, possibly because the file is set to + # read-only. If that's why, make it writable and try the op + # again. + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWRITE) + fn(path) + + shutil.rmtree(dest, onerror=_on_error) + else: + if not os.access(dest, os.W_OK): + # Attempt to make the file writable before deleting it. + os.chmod(dest, stat.S_IWRITE) + os.unlink(dest) + + shutil.copy2(source, dest) + + +if __name__ == '__main__': + main() diff --git a/engine/src/flutter/tools/fuchsia/toolchain/default_tools.gni b/engine/src/flutter/tools/fuchsia/toolchain/default_tools.gni new file mode 100644 index 0000000000..d388b79f98 --- /dev/null +++ b/engine/src/flutter/tools/fuchsia/toolchain/default_tools.gni @@ -0,0 +1,28 @@ +# Copyright 2013 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. + +# Each toolchain must define "stamp" and "copy" tools, +# but they are always the same in every toolchain. +stamp_command = "touch {{output}}" +stamp_description = "STAMP {{output}}" + +# mtime on directories may not reflect the latest of a directory. For example in +# popular file systems, modifying a file in a directory does not affect mtime of +# the directory. Because of this, disable directory copy for incremental +# correctness. See https://fxbug.dev/73250. +copy_command = "if [ -d {{source}} ]; then echo 'Tool \"copy\" does not support directory copies'; exit 1; fi; " + +# We use link instead of copy; the way "copy" tool is being used is +# compatible with links since Ninja is tracking changes to the source. +if (host_os == "mac") { + # `cp -af` does not correctly preserve mtime (the nanoseconds are truncated to + # microseconds) which causes spurious ninja rebuilds. As a result, shell to a + # helper to copy rather than calling cp -r. See https://fxbug.dev/56376#c5. + copy_command += "ln -f {{source}} {{output}} 2>/dev/null || (" + + rebase_path("//flutter/tools/fuchsia/toolchain/copy.py") + + " {{source}} {{output}})" +} else { + copy_command += "ln -f {{source}} {{output}} 2>/dev/null || (rm -f {{output}} && cp -af {{source}} {{output}})" +} +copy_description = "COPY {{source}} {{output}}"