diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8e97d6f..55d81d05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,6 +195,26 @@ jobs: bazel build -c opt --lockfile_mode=${{ env.BAZEL_LOCKFILE_MODE }} --repo_env=ANDROID_NDK_HOME="$ANDROID_NDK_HOME" --android_platforms=@rules_android//:arm64-v8a //:consumer_rules_r8 python3 ../../tools/ci/consumer_rules_check.py + - name: Plugin hub guard against private loads + run: python3 tools/ci/plugin_hub_boundary_guard.py --selftest + + - name: Example plugin hub public-load guard + working-directory: examples/pub_plugins + run: python3 ../../tools/ci/plugin_hub_boundary_guard.py @flutter_plugins//:generated_plugin_registrant_src + + - name: Consumer plugin hubs public-load guard + working-directory: tests/consumer + run: python3 ../../tools/ci/plugin_hub_boundary_guard.py @flutter_plugins//:generated_plugin_registrant_src @composed_plugins//:generated_plugin_registrant_src @composed_override_plugins//:generated_plugin_registrant_src + + - name: Consumer hand-wired app mode and DEX checks + working-directory: tests/consumer + run: | + set -euo pipefail + apk="bazel-bin/hand_wired_app/hand_wired_app_unsigned.apk" + bazel build -c fastbuild --@rules_flutter_core//flutter:mode=debug --lockfile_mode=${{ env.BAZEL_LOCKFILE_MODE }} --repo_env=ANDROID_NDK_HOME="$ANDROID_NDK_HOME" --android_platforms=@rules_android//:arm64-v8a //hand_wired_app:hand_wired_app + python3 ../../tools/ci/hand_wired_dex_check.py debug "$apk" + bazel build -c opt --@rules_flutter_core//flutter:mode=release --lockfile_mode=${{ env.BAZEL_LOCKFILE_MODE }} --repo_env=ANDROID_NDK_HOME="$ANDROID_NDK_HOME" --android_platforms=@rules_android//:arm64-v8a //hand_wired_app:hand_wired_app + python3 ../../tools/ci/hand_wired_dex_check.py release "$apk" # Composed app: the APK check fails the build on a planted mismatch. - name: Consumer composed app release APK check working-directory: tests/consumer @@ -264,6 +284,10 @@ jobs: set -o pipefail env -u ANDROID_HOME -u ANDROID_NDK_HOME bazel test //... --lockfile_mode=${{ env.BAZEL_LOCKFILE_MODE }} 2>&1 | tee ../bazel_test_android.log + - name: Registrant parity tests in debug mode + working-directory: rules_flutter_android + run: bazel test --@rules_flutter_core//flutter:mode=debug --lockfile_mode=${{ env.BAZEL_LOCKFILE_MODE }} //tests/registrant_parity:all + # The registrant parity goldens, recaptured from this Flutter SDK, must # still match: a template change in flutter_tools fails here. - name: Registrant parity recapture diff --git a/QUICKSTART.md b/QUICKSTART.md index cbf5c8c6..aa488269 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -368,7 +368,7 @@ relative-import helpers: ```python # integration_test/BUILD.bazel -load("@rules_flutter_core//flutter:defs.bzl", "flutter_debug_core") +load("@rules_flutter_core//flutter:composition.bzl", "flutter_debug_core") flutter_debug_core( name = "prebuilt_core", @@ -618,41 +618,25 @@ shows recipe inputs and ABI-specific native contributions. ## Consumer-owned stock android_binary -A consumer may package core outputs with its own stock `android_binary`. The -four output groups are provided by `OutputGroupInfo` on a configured -`flutter_core` target; `FlutterArtifactInfo` is a separate typed provider, not -the output-group provider. Use `filegroup(output_group = ...)` to select the -outputs. `flutter_assets` is the asset tree, `flutter_engine` is the whole -selected engine-library set (which may contain multiple libraries for an ABI), -`flutter_libapp` is release AOT output and empty in debug, and -`flutter_embedding` is the selected mode's embedding jar. Use the mode-selecting -embedding output directly rather than a hard-coded Maven release label. - -For example, one group can be selected from the configured core like this: - -```python -filegroup( - name = "direct_engine", - srcs = [":core"], - output_group = "flutter_engine", -) -``` - -Select the other three groups with the same pattern and their respective -`output_group` names. The checked fixture below shows those groups flowing into -the stock binary together with the mode-selected Java embedding and raw -ABI-pinned native inputs. - -The checked stock-binary fixture is -[`tests/consumer/composed_app/BUILD.bazel`](tests/consumer/composed_app/BUILD.bazel), -target `//composed_app:direct_apk`. It shows group selection, mode-selected -embedding import with the explicit AndroidX dependency closure in -[`tests/consumer/embedding_deps.bzl`](tests/consumer/embedding_deps.bzl), raw -ABI-pinned native inputs, and a stock `android_binary`. The fixture's -`direct_native` helper is consumer-owned demonstration code, not a public ruleset -API. Android packaging owns normal JNI and strip handling; use the raw -ABI-pinned strip path as appropriate for the consumer's native files. Do not -assume a single engine `.so` or a minimal embedding classpath. +Use the convenience rules from `defs.bzl` for standard apps. A consumer that +needs to own Android packaging can instead combine the public building blocks: +`flutter_core` exposes the `flutter_assets`, `flutter_engine`, and +`flutter_libapp` output groups. The engine group may contain several libraries +per ABI, and `flutter_libapp` is empty in debug mode. The Android composition +module provides `flutter_plugin_registrant`, `flutter_build_config`, and the +mode-selected `embedding_jar`; use `flutter_embedding_deps(maven_repo)` for the +embedding's explicit AndroidX classpath. Here `flutter_assets` is `flutter_core`'s +output group, not the `flutter_assets` rule. Stock `rules_android` rules then package those targets; for native +packaging, see [`tests/consumer/composed_app:direct_apk`](tests/consumer/composed_app/BUILD.bazel). + +[`tests/consumer/hand_wired_app/BUILD.bazel`](tests/consumer/hand_wired_app/BUILD.bazel) +demonstrates plugin, Java, and Maven wiring without plugin extensions or +convenience macros; it is not a complete APK because it does not package the +Flutter native libraries. The registrant and build-config rules each produce +one Java source file suitable for `android_library.srcs`, and development +plugin code is included only in debug. Consumers using Kotlin Multiplatform +Maven artifacts must name the platform artifact (such as an `-jvm` artifact) +because `rules_jvm_external` does not resolve Gradle module metadata. ## ABI configuration and switching diff --git a/README.md b/README.md index 0c4904a6..5e3f8a4e 100644 --- a/README.md +++ b/README.md @@ -53,31 +53,42 @@ declarations are needed. ### Composition and ABI selection -Declare one `flutter_core` from the core module per Bazel package; it owns that -package's app outputs and package/test outputs. Declare -`flutter_plugin_deps_guards` once in the package that owns the plugin graph to -check its committed Maven segment. `flutter_android_app` is the standard helper -entrypoint. -`flutter_android_inputs`, `flutter_apk_check`, `flutter_android_manifest`, -`flutter_assets_dir`, and `flutter_native_contribution` support custom -composition. +The convenience API is the default: load `flutter_core`, `flutter_package`, +`flutter_pubspec`, `flutter_test`, and `flutter_test_suite` from +`@rules_flutter_core//flutter:defs.bzl`, and `flutter_android_app` and related +Android helpers from `@rules_flutter_android//flutter:defs.bzl`. For deliberate +hand-wiring, the public `composition.bzl` modules expose lower-level providers, +rules, and targets. `flutter_pubspec` remains in core `defs.bzl` because +`flutter_package` requires it. + +Core's Flutter SDK repository exposes `@flutter_sdk//:flutter.version.json` as +read-only version metadata. Android's embedding repositories derive their SDK +version from that file; consumers do not need to name those repository labels. Select APK ABIs with ordinary `--android_platforms`. Use one APK label for each existing app/mode variant and separate build commands for fat or single-ABI configurations. Capture each APK before the next configuration because the materialized APK path may be shared. -A consumer-owned stock `android_binary` may consume configured core targets' -`OutputGroupInfo`: `flutter_assets` is the bundle tree, `flutter_engine` is the -complete selected engine-library set, `flutter_libapp` is release AOT (empty in -debug), and `flutter_embedding` is the mode-selected embedding jar. The engine -group can contain multiple libraries for one ABI. `FlutterArtifactInfo` is a -separate typed provider. See the checked stock-binary example in -[`tests/consumer/composed_app/BUILD.bazel`](tests/consumer/composed_app/BUILD.bazel), -target `//composed_app:direct_apk`. Consumers own Android packaging and native -library handling, including strip behavior required by their packaging graph. - -`flutter_debug_core` declares a debug artifact of an app's `flutter_core` built from another entrypoint, such as a test file under `integration_test/`, with its own `dart_defines`. Dart files outside `lib/` that the entrypoint imports go in `srcs`; an undeclared import fails the compile. The app's package inputs, mode-free bundle and debug engine are reused, so beyond the app's own outputs it adds only its kernel and a bundle copy. It is debug only: a release configuration reports it incompatible, and it is `manual` unless `tags` overrides that, so `bazel build //...` skips it under debug. Build it with `-c fastbuild --@rules_flutter_core//flutter:mode=debug` and an Android `--platforms`. +Core output groups remain available to hand-wired consumers: `flutter_assets`, +`flutter_engine` (possibly several libraries per ABI), and `flutter_libapp` +(empty in debug mode). Embedding is owned by the Android ruleset. Use +`@rules_flutter_android//flutter:embedding_jar` for the mode-selected jar; +`flutter_embedding_deps(maven_repo)` supplies its explicit AndroidX classpath. +The [`tests/consumer/hand_wired_app/BUILD.bazel`](tests/consumer/hand_wired_app/BUILD.bazel) +fixture demonstrates plugin, Java, and Maven wiring, but is not a complete APK. +For native packaging, see +[`tests/consumer/composed_app:direct_apk`](tests/consumer/composed_app/BUILD.bazel). + +`flutter_debug_core` is a lower-level core composition rule. It declares a debug +artifact of an app's `flutter_core` built from another entrypoint, such as an +integration-test file, with its own `dart_defines`. Dart files outside `lib/` +that the entrypoint imports go in `srcs`; an undeclared import fails the +compile. The app's package inputs, mode-free bundle, and debug engine are reused. +It is debug-only and `manual` unless `tags` overrides that. Build it with +`-c fastbuild --@rules_flutter_core//flutter:mode=debug` and an Android +`--platforms`. + One measured warm four-command sequence took 1.500 seconds with the former suffixed labels and 16.165 seconds with one label under successive ABI configurations. These timings are scoped to one machine/workflow, not a general speed promise. Expensive Flutter/native actions remained reusable, while Android packaging reran. Disk/remote-cache switching speed was not measured; shared materialized paths do not inherently collide in action caches. diff --git a/examples/demo_app/MODULE.bazel.lock b/examples/demo_app/MODULE.bazel.lock index 8ff78966..2018548d 100644 --- a/examples/demo_app/MODULE.bazel.lock +++ b/examples/demo_app/MODULE.bazel.lock @@ -409,7 +409,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "HFu4wseopxonsvDUd/4S1JEEWdwTssTyzt3rsp61Cg8=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -431,7 +431,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "NAsegc0NGgbnISf1WUvMP9+cZAMUJEHXC+unOFzUAKo=", "recordedInputs": [ "REPO_MAPPING:,flutter_maven rules_jvm_external++maven+flutter_maven", @@ -440,8 +440,6 @@ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:sameri11_rules_flutter_android+,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:sameri11_rules_flutter_android+,androidndk_cmake sameri11_rules_flutter_android++android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -453,8 +451,8 @@ "x86_64", "armeabi-v7a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+pub//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+pub//:package_config.json", "coursier_options": [], @@ -469,7 +467,6 @@ "rive_native": "{\"bzl\":\"@@//bazel/flutter:rive_native.bzl\",\"macro\":\"rive_native_recipe\",\"root\":true}", "sqlite3": "{\"bzl\":\"@@//bazel/flutter:sqlite3.bzl\",\"macro\":\"sqlite3_recipe\",\"root\":true}" }, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -478,9 +475,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "PVRq/fIGaef1uAaEjTR0LCJbig9Z96itWnzuvhhPV74=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -497,18 +517,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/examples/local_plugin/MODULE.bazel.lock b/examples/local_plugin/MODULE.bazel.lock index 15e46bef..d66596e9 100644 --- a/examples/local_plugin/MODULE.bazel.lock +++ b/examples/local_plugin/MODULE.bazel.lock @@ -409,7 +409,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "mXQ4Q6FohJ5fXR4FTAao+VwkF3Gfgb646BQodIC/hD8=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -431,7 +431,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "G8kgLfLv/g4JO70/Fz65CA3gz9V9VxPrddSvVKtpw54=", "recordedInputs": [ "REPO_MAPPING:,flutter_maven rules_jvm_external++maven+flutter_maven", @@ -440,8 +440,6 @@ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:sameri11_rules_flutter_android+,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:sameri11_rules_flutter_android+,androidndk_cmake sameri11_rules_flutter_android++android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -451,8 +449,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+pub//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+pub//:package_config.json", "coursier_options": [], @@ -464,7 +462,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -473,9 +470,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "mhGaagnJ1rxsIhLvM71ruzvKd+zmw0qH6YhJRGCfdjM=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -492,18 +512,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/examples/no_plugins/MODULE.bazel.lock b/examples/no_plugins/MODULE.bazel.lock index 68e439e9..623972bb 100644 --- a/examples/no_plugins/MODULE.bazel.lock +++ b/examples/no_plugins/MODULE.bazel.lock @@ -409,7 +409,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "gihfLsx8YMM+v3GPdKHQB+XDY0mBmcT3vsEGUwsp5YE=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -429,9 +429,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "qgOPHTIJehHE7C5edcqknU9pqU6zUuSV2IGyzjt0tf8=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -448,18 +471,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/examples/no_plugins/android/app/BUILD.bazel b/examples/no_plugins/android/app/BUILD.bazel index fe960632..fe8cabe0 100644 --- a/examples/no_plugins/android/app/BUILD.bazel +++ b/examples/no_plugins/android/app/BUILD.bazel @@ -1,6 +1,7 @@ # Android targets for the plugin-free app. -load("@rules_flutter_android//flutter:defs.bzl", "flutter_android_app", "flutter_android_inputs") +load("@rules_flutter_android//flutter:composition.bzl", "flutter_android_inputs") +load("@rules_flutter_android//flutter:defs.bzl", "flutter_android_app") load("@rules_kotlin//kotlin:android.bzl", "kt_android_library") package(default_visibility = ["//visibility:public"]) diff --git a/examples/no_plugins/integration_test/BUILD.bazel b/examples/no_plugins/integration_test/BUILD.bazel index 02010896..f11beaa0 100644 --- a/examples/no_plugins/integration_test/BUILD.bazel +++ b/examples/no_plugins/integration_test/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_flutter_core//flutter:defs.bzl", "flutter_debug_core") +load("@rules_flutter_core//flutter:composition.bzl", "flutter_debug_core") package(default_visibility = ["//visibility:public"]) diff --git a/examples/pub_plugins/MODULE.bazel.lock b/examples/pub_plugins/MODULE.bazel.lock index a53e2bea..75b3b663 100644 --- a/examples/pub_plugins/MODULE.bazel.lock +++ b/examples/pub_plugins/MODULE.bazel.lock @@ -409,7 +409,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "CWMaitCzriuNa8mQm9VlXo9SoC7z68RHk02zkINH09A=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -431,7 +431,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "mW8ky7T5PDBsdwo6w2Q5gOopJ+4T57XXK8sGug9bSvc=", "recordedInputs": [ "REPO_MAPPING:,flutter_maven rules_jvm_external++maven+flutter_maven", @@ -440,8 +440,6 @@ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:sameri11_rules_flutter_android+,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:sameri11_rules_flutter_android+,androidndk_cmake sameri11_rules_flutter_android++android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -451,8 +449,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+pub//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+pub//:package_config.json", "coursier_options": [], @@ -464,7 +462,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -473,9 +470,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "7g5j5szAjo79jAswLX0VYMWhMYvsF2r2WEYs8ccrN/k=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -492,18 +512,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/rules_flutter_android/BUILD.bazel b/rules_flutter_android/BUILD.bazel index 943f08c0..64246949 100644 --- a/rules_flutter_android/BUILD.bazel +++ b/rules_flutter_android/BUILD.bazel @@ -1,5 +1,5 @@ load("@buildifier_prebuilt//:rules.bzl", "buildifier", "buildifier_test") -load("//flutter:defs.bzl", "flutter_assets_dir") +load("//flutter:composition.bzl", "flutter_assets_dir") # The root package exists so //:buildifier_test has a label in it to locate the # tree it should check. diff --git a/rules_flutter_android/MODULE.bazel b/rules_flutter_android/MODULE.bazel index f07e2f38..e68965ad 100644 --- a/rules_flutter_android/MODULE.bazel +++ b/rules_flutter_android/MODULE.bazel @@ -12,9 +12,10 @@ local_path_override( ) flutter = use_extension("@rules_flutter_core//flutter:extensions.bzl", "flutter") +use_repo(flutter, "flutter_sdk") -# The embedding jars the Android rules compile against, by mode. -use_repo(flutter, "flutter_embedding_debug", "flutter_embedding_release") +flutter_android_embedding = use_extension("//flutter/private:embedding_repo.bzl", "flutter_android_embedding") +use_repo(flutter_android_embedding, "flutter_embedding_debug", "flutter_embedding_release") # Pin for developing this repository as the root module. A dev dependency, so # consumers ignore it, as they ignore core's own pin. diff --git a/rules_flutter_android/MODULE.bazel.lock b/rules_flutter_android/MODULE.bazel.lock index f40f09b7..393edd5a 100644 --- a/rules_flutter_android/MODULE.bazel.lock +++ b/rules_flutter_android/MODULE.bazel.lock @@ -300,7 +300,7 @@ "moduleExtensions": { "//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "BSiKVNTHy3EXCSxrVE2lcJp20b5h/Z0f4xjsXF45WZ0=", "recordedInputs": [ "REPO_MAPPING:,rules_android rules_android+", @@ -322,7 +322,7 @@ }, "//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "9owEpU8N8w2F4asd++mkiTNLiSzbTw8JI0PGfMv4MyM=", "recordedInputs": [ "REPO_MAPPING:,parity_maven rules_jvm_external++maven+parity_maven", @@ -335,8 +335,6 @@ "REPO_MAPPING:,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:,androidndk_cmake +android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -346,8 +344,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@//flutter:embedding_jar_debug", + "embedding_release": "@@//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+registrant_parity_no_plugins//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+registrant_parity_no_plugins//:package_config.json", "coursier_options": [], @@ -359,7 +357,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@+android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -371,8 +368,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@//flutter:embedding_jar_debug", + "embedding_release": "@@//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+registrant_parity_mixed//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+registrant_parity_mixed//:package_config.json", "coursier_options": [], @@ -384,7 +381,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@+android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -396,8 +392,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@//flutter:embedding_jar_debug", + "embedding_release": "@@//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_default//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_default//:package_config.json", "coursier_options": [], @@ -409,7 +405,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@+android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -421,8 +416,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@//flutter:embedding_jar_debug", + "embedding_release": "@@//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_direct//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_direct//:package_config.json", "coursier_options": [], @@ -434,7 +429,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@+android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -446,8 +440,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@//flutter:embedding_jar_debug", + "embedding_release": "@@//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_dev_override//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+registrant_parity_federated_dev_override//:package_config.json", "coursier_options": [], @@ -459,7 +453,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@+android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -468,6 +461,29 @@ } } }, + "//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "JpFePUNenAgojvGWTEykSWOR1tW+yKsbSwtNkwh8Zis=", + "recordedInputs": [ + "REPO_MAPPING:,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { "general": { "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=", @@ -577,7 +593,7 @@ }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "yrcrs3q9AyVzH00WG98mD1OpGXB4f6VMs6fbGNdHd28=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -594,18 +610,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/rules_flutter_android/flutter/BUILD.bazel b/rules_flutter_android/flutter/BUILD.bazel index 5aaf63a9..7190b29b 100644 --- a/rules_flutter_android/flutter/BUILD.bazel +++ b/rules_flutter_android/flutter/BUILD.bazel @@ -2,8 +2,35 @@ load("//flutter:native_build_env.bzl", "native_build_env") package(default_visibility = ["//visibility:public"]) -# Named as a source by //tools/ci:key_portability_check. -exports_files(["native_build_env.bzl"]) +exports_files([ + "native_build_env.bzl", + "composition.bzl", +]) + +filegroup( + name = "starlark_sources", + srcs = glob(["*.bzl"]) + ["BUILD.bazel"], + visibility = ["//tools/ci:__pkg__"], +) + +# The embedding belongs to this ruleset; consumers need not import the repos. +filegroup( + name = "embedding_jar_debug", + srcs = ["@flutter_embedding_debug//jar:file"], +) + +filegroup( + name = "embedding_jar_release", + srcs = ["@flutter_embedding_release//jar:file"], +) + +filegroup( + name = "embedding_jar", + srcs = select({ + "@rules_flutter_core//flutter:mode_debug": [":embedding_jar_debug"], + "@rules_flutter_core//flutter:mode_release": [":embedding_jar_release"], + }), +) # The Android SDK a native build hook reads. Pass this to a flutter_core call # explicitly when package hooks need the SDK. diff --git a/rules_flutter_android/flutter/composition.bzl b/rules_flutter_android/flutter/composition.bzl new file mode 100644 index 00000000..347695c0 --- /dev/null +++ b/rules_flutter_android/flutter/composition.bzl @@ -0,0 +1,76 @@ +"""Public building blocks for hand-wired Flutter Android targets.""" + +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") +load("//flutter/private:android.bzl", _flutter_android_manifest = "flutter_android_manifest", _flutter_assets_dir = "flutter_assets_dir") +load("//flutter/private:android_inputs_rules.bzl", _flutter_android_inputs = "flutter_android_inputs") +load("//flutter/private:apk_check.bzl", _flutter_apk_check = "flutter_apk_check") +load("//flutter/private:embedding.bzl", _FLUTTER_EMBEDDING_ARTIFACTS = "FLUTTER_EMBEDDING_ARTIFACTS", _flutter_embedding_deps = "flutter_embedding_deps") +load("//flutter/private:maven_identity.bzl", _FlutterMavenRepositoryInfo = "FlutterMavenRepositoryInfo", _flutter_maven_repository = "flutter_maven_repository") +load("//flutter/private:native_files.bzl", _FlutterNativeFilesByAbiInfo = "FlutterNativeFilesByAbiInfo", _flutter_native_files = "flutter_native_files", _flutter_native_files_by_abi = "flutter_native_files_by_abi") +load("//flutter/private:native_strip.bzl", _android_native_files = "android_native_files") +load("//flutter/private:registrant.bzl", "render_android_registrant") + +FlutterNativeFilesByAbiInfo = _FlutterNativeFilesByAbiInfo +FlutterMavenRepositoryInfo = _FlutterMavenRepositoryInfo +android_native_files = _android_native_files +flutter_native_files = _flutter_native_files +flutter_native_files_by_abi = _flutter_native_files_by_abi +flutter_maven_repository = _flutter_maven_repository +flutter_android_inputs = _flutter_android_inputs +flutter_android_manifest = _flutter_android_manifest +flutter_assets_dir = _flutter_assets_dir +flutter_apk_check = _flutter_apk_check +flutter_embedding_deps = _flutter_embedding_deps +FLUTTER_EMBEDDING_ARTIFACTS = _FLUTTER_EMBEDDING_ARTIFACTS + +_MODE = Label("@rules_flutter_core//flutter:mode") + +def _registrant_impl(ctx): + mode = ctx.attr._mode[BuildSettingInfo].value + entries = sorted(ctx.attr.plugins + (ctx.attr.dev_plugins if mode == "debug" else []), key = lambda entry: entry.split("|", 1)[0]) + parsed = [] + for entry in entries: + parts = entry.split("|", 1) + if len(parts) != 2: + fail("{}: plugin entries must be name|qualified.class, got {}".format(ctx.label, entry)) + parsed.append((parts[0], parts[1])) + output = ctx.actions.declare_file(ctx.label.name + "/GeneratedPluginRegistrant.java") + ctx.actions.write(output, render_android_registrant(parsed)) + return [DefaultInfo(files = depset([output]))] + +flutter_plugin_registrant = rule( + implementation = _registrant_impl, + doc = "Generates GeneratedPluginRegistrant.java from plugin entries; dev_plugins are included only in debug mode.", + attrs = { + "plugins": attr.string_list(doc = "Plugin entries in name|fully.qualified.Class format."), + "dev_plugins": attr.string_list(doc = "Development plugin entries in name|fully.qualified.Class format; included only in debug mode."), + "_mode": attr.label(default = _MODE, providers = [BuildSettingInfo]), + }, +) + +def _build_config_impl(ctx): + mode = ctx.attr._mode[BuildSettingInfo].value + debug = mode == "debug" + source = ( + "package {};\n\n".format(ctx.attr.package_name) + + "public final class BuildConfig {\n" + + " public static final boolean DEBUG = {};\n".format("true" if debug else "false") + + " public static final String BUILD_TYPE = {};\n".format(json.encode("debug" if debug else "release")) + + " public static final String LIBRARY_PACKAGE_NAME = {};\n".format(json.encode(ctx.attr.package_name)) + + "".join(ctx.attr.fields) + + " private BuildConfig() {}\n" + + "}\n" + ) + output = ctx.actions.declare_file(ctx.label.name + "/BuildConfig.java") + ctx.actions.write(output, source) + return [DefaultInfo(files = depset([output]))] + +flutter_build_config = rule( + implementation = _build_config_impl, + doc = "Generates BuildConfig.java using the package name, selected mode, and extra field declaration lines.", + attrs = { + "package_name": attr.string(mandatory = True, doc = "Java package name for the generated BuildConfig class."), + "fields": attr.string_list(doc = "Raw Java field declaration lines; each entry is emitted verbatim followed by a newline."), + "_mode": attr.label(default = _MODE, providers = [BuildSettingInfo]), + }, +) diff --git a/rules_flutter_android/flutter/defs.bzl b/rules_flutter_android/flutter/defs.bzl index 4df28177..4df154bc 100644 --- a/rules_flutter_android/flutter/defs.bzl +++ b/rules_flutter_android/flutter/defs.bzl @@ -1,17 +1,7 @@ -"""Supported BUILD-file API for the Android half of Flutter Consumer Modules. +"""Supported convenience BUILD API for Flutter Android consumer modules.""" -Platform-neutral rules load from `@rules_flutter_core//flutter:defs.bzl`. This -entrypoint adds Android packaging and native contribution rules. -""" - -load( - "@rules_flutter_core//flutter:defs.bzl", - _core_flutter_assets = "flutter_assets", -) -load("//flutter/private:android.bzl", _flutter_android_manifest = "flutter_android_manifest", _flutter_assets_dir = "flutter_assets_dir") +load("@rules_flutter_core//flutter:composition.bzl", _core_flutter_assets = "flutter_assets") load("//flutter/private:android_app.bzl", _FLUTTER_DEBUG_UNCOMPRESSED_ASSETS = "FLUTTER_DEBUG_UNCOMPRESSED_ASSETS", _flutter_android_app = "flutter_android_app") -load("//flutter/private:android_inputs_rules.bzl", _flutter_android_inputs = "flutter_android_inputs") -load("//flutter/private:apk_check.bzl", _flutter_apk_check = "flutter_apk_check") load("//flutter/private:plugin_deps_guards.bzl", _flutter_plugin_deps_guards = "flutter_plugin_deps_guards") load("//flutter/private:recipe.bzl", _flutter_native_contribution = "flutter_native_contribution") @@ -21,12 +11,7 @@ def flutter_assets(native_build_env = _ANDROID_NATIVE_BUILD_ENV, **kwargs): """Declares flutter_assets with the Android SDK native build environment.""" _core_flutter_assets(native_build_env = native_build_env, **kwargs) -# Explicit assignments form the curated module API. -flutter_native_contribution = _flutter_native_contribution -flutter_android_inputs = _flutter_android_inputs -flutter_apk_check = _flutter_apk_check -flutter_android_manifest = _flutter_android_manifest -flutter_assets_dir = _flutter_assets_dir flutter_android_app = _flutter_android_app flutter_plugin_deps_guards = _flutter_plugin_deps_guards +flutter_native_contribution = _flutter_native_contribution FLUTTER_DEBUG_UNCOMPRESSED_ASSETS = _FLUTTER_DEBUG_UNCOMPRESSED_ASSETS diff --git a/rules_flutter_android/flutter/native_build_env.bzl b/rules_flutter_android/flutter/native_build_env.bzl index eacfe7f7..3bf1b5da 100644 --- a/rules_flutter_android/flutter/native_build_env.bzl +++ b/rules_flutter_android/flutter/native_build_env.bzl @@ -5,7 +5,7 @@ hooks find the SDK through ANDROID_HOME, so this rule exports it for the action that runs them. """ -load("@rules_flutter_core//flutter:defs.bzl", "FlutterNativeBuildEnvInfo") +load("@rules_flutter_core//flutter:composition.bzl", "FlutterNativeBuildEnvInfo") def _native_build_env_impl(ctx): sdk = ctx.file._android_sdk diff --git a/rules_flutter_android/flutter/private/BUILD.bazel b/rules_flutter_android/flutter/private/BUILD.bazel index 0a782392..f8f7b70b 100644 --- a/rules_flutter_android/flutter/private/BUILD.bazel +++ b/rules_flutter_android/flutter/private/BUILD.bazel @@ -3,6 +3,12 @@ load(":integration_test_runner.bzl", "flutter_android_integration_test_kernel", package(default_visibility = ["//visibility:private"]) +filegroup( + name = "starlark_sources", + srcs = glob(["*.bzl"]) + ["BUILD.bazel"], + visibility = ["//tools/ci:__pkg__"], +) + # The action sources //tools/ci:key_portability_check reads as srcs. That guard # parses their source, which requires a label, not a load. exports_files( @@ -28,6 +34,11 @@ exports_files( visibility = ["//visibility:public"], ) +exports_files( + ["embedding_repo.bzl"], + visibility = ["//:__pkg__"], +) + # Attribute defaults of rules defined under //flutter. Those rules are # instantiated in consumer packages, so //flutter:__subpackages__ is enough. exports_files( diff --git a/rules_flutter_android/flutter/private/android.bzl b/rules_flutter_android/flutter/private/android.bzl index 7a37e051..8b1bdc0b 100644 --- a/rules_flutter_android/flutter/private/android.bzl +++ b/rules_flutter_android/flutter/private/android.bzl @@ -1,6 +1,6 @@ """Android manifest and asset-path helpers used by composed app targets.""" -load("@rules_flutter_core//flutter:defs.bzl", "FlutterArtifactInfo", "FlutterPubspecInfo") +load("@rules_flutter_core//flutter:composition.bzl", "FlutterArtifactInfo", "FlutterPubspecInfo") visibility(["//flutter"]) @@ -32,16 +32,16 @@ def _flutter_android_manifest_impl(ctx): args = ctx.actions.args() pubspec = ctx.attr.pubspec[FlutterPubspecInfo] args.add(ctx.file._injector) - args.add("--version-name", pubspec.version_name) - args.add("--version-code", pubspec.version_code) + args.add("--version-name", pubspec.build_name) + args.add("--version-code", pubspec.build_number) args.add("--manifest", ctx.file.manifest) args.add("--out", out) ctx.actions.run_shell( command = 'exec python3 "$@"', arguments = [args], inputs = [ - pubspec.version_name, - pubspec.version_code, + pubspec.build_name, + pubspec.build_number, ctx.file.manifest, ctx.file._injector, ], diff --git a/rules_flutter_android/flutter/private/android_app.bzl b/rules_flutter_android/flutter/private/android_app.bzl index 07388a95..97ec288e 100644 --- a/rules_flutter_android/flutter/private/android_app.bzl +++ b/rules_flutter_android/flutter/private/android_app.bzl @@ -8,7 +8,7 @@ engine strip, the AOT library and the asset copy are built once. """ load("@rules_android//rules:rules.bzl", "android_binary", "android_library") -load("@rules_flutter_core//flutter:defs.bzl", "flutter_debug_core") +load("@rules_flutter_core//flutter:composition.bzl", "flutter_debug_core") load(":abis.bzl", "MIN_SDK") load(":android.bzl", "flutter_android_assets", "flutter_android_manifest", "flutter_assets_dir") load(":android_inputs_rules.bzl", "flutter_android_inputs") diff --git a/rules_flutter_android/flutter/private/android_inputs_rules.bzl b/rules_flutter_android/flutter/private/android_inputs_rules.bzl index 88796ed2..cb975ab7 100644 --- a/rules_flutter_android/flutter/private/android_inputs_rules.bzl +++ b/rules_flutter_android/flutter/private/android_inputs_rules.bzl @@ -17,7 +17,7 @@ load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cpp_toolchain", "use_cc_toolch load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load("@rules_flutter_core//flutter:abis.bzl", "ABIS", "ABI_PLATFORM_ATTRS", "abi_from_platform") -load("@rules_flutter_core//flutter:defs.bzl", "FlutterArtifactInfo") +load("@rules_flutter_core//flutter:composition.bzl", "FlutterArtifactInfo") load("@rules_java//java:defs.bzl", "java_import") load(":android.bzl", "flutter_android_assets") load(":embedding.bzl", "ANNOTATION_JVM", "flutter_embedding_deps") @@ -40,22 +40,6 @@ FlutterSplitNativeInfo = provider( }, ) -def _core_file_impl(ctx): - info = ctx.attr.core[FlutterArtifactInfo] - return [DefaultInfo(files = depset([getattr(info, ctx.attr.field)]))] - -# Passes one File of the core's provider through unchanged, so a java_import or -# a consumer can name it. The embedding jar is the core's own output, so nothing here -# runs an action. -_core_file = rule( - implementation = _core_file_impl, - doc = "One File of a flutter_core split's FlutterArtifactInfo.", - attrs = { - "core": attr.label(mandatory = True, providers = [FlutterArtifactInfo]), - "field": attr.string(mandatory = True, values = ["embedding_jar"]), - }, -) - def _split_providers(ctx, cc_toolchain, libraries, split): feature_configuration = cc_common.configure_features( ctx = ctx, @@ -258,15 +242,9 @@ def flutter_android_inputs(name, core, plugins, maven_repo = None, tags = [], vi embedding_deps = flutter_embedding_deps(maven_repo) validation = [] - _core_file( - name = name + "_core_embedding", - core = core, - field = "embedding_jar", - tags = helper_tags, - ) java_import( name = name + "_embedding", - jars = [":" + name + "_core_embedding"], + jars = [Label("//flutter:embedding_jar")], deps = embedding_deps, tags = helper_tags, ) diff --git a/rules_flutter_android/flutter/private/embedding_repo.bzl b/rules_flutter_android/flutter/private/embedding_repo.bzl new file mode 100644 index 00000000..fadc7268 --- /dev/null +++ b/rules_flutter_android/flutter/private/embedding_repo.bzl @@ -0,0 +1,40 @@ +"""Repositories for the Java embedding used by Android consumers.""" + +visibility(["//:__pkg__"]) + +_BASE = "https://storage.googleapis.com/download.flutter.io/io/flutter" + +def _embedding_impl(ctx): + version = json.decode(ctx.read(ctx.path(ctx.attr.version_file))) + artifact = "flutter_embedding_{}".format(ctx.attr.mode) + url = "{}/{}/1.0.0-{}/{}-1.0.0-{}.jar".format( + _BASE, + artifact, + version["engineRevision"], + artifact, + version["engineContentHash"], + ) + ctx.download(url = url, output = "jar/{}.jar".format(artifact), canonical_id = url) + ctx.file("jar/BUILD.bazel", """filegroup( + name = "file", + srcs = ["{}.jar"], + visibility = ["//visibility:public"], +) +""".format(artifact)) + +flutter_embedding_jar = repository_rule( + implementation = _embedding_impl, + attrs = { + "mode": attr.string(mandatory = True, values = ["debug", "release"]), + "version_file": attr.label( + allow_single_file = True, + default = Label("@flutter_sdk//:flutter.version.json"), + ), + }, +) + +def _embedding_extension_impl(_ctx): + for mode in ["debug", "release"]: + flutter_embedding_jar(name = "flutter_embedding_" + mode, mode = mode) + +flutter_android_embedding = module_extension(implementation = _embedding_extension_impl) diff --git a/rules_flutter_android/flutter/private/integration_test_runner.bzl b/rules_flutter_android/flutter/private/integration_test_runner.bzl index 3d2553ab..08595953 100644 --- a/rules_flutter_android/flutter/private/integration_test_runner.bzl +++ b/rules_flutter_android/flutter/private/integration_test_runner.bzl @@ -1,6 +1,6 @@ """Dart host runner for Android integration-test APKs.""" -load("@rules_flutter_core//flutter:defs.bzl", "FlutterIntegrationTestInfo") +load("@rules_flutter_core//flutter:composition.bzl", "FlutterIntegrationTestInfo") _TOOLCHAIN = "@rules_flutter_core//flutter:toolchain_type" @@ -105,7 +105,7 @@ flutter_android_integration_test_kernel = rule( implementation = _kernel_impl, attrs = { "src": attr.label(mandatory = True, allow_single_file = [".dart"]), - "_decoder_srcs": attr.label(default = Label("@rules_flutter_core//flutter/private:device_decoder_library")), + "_decoder_srcs": attr.label(default = Label("@rules_flutter_core//flutter:device_test_decoder")), "_logic_src": attr.label(default = Label("//flutter/private:integration_test_runner_logic.dart"), allow_single_file = True), }, toolchains = [_TOOLCHAIN], diff --git a/rules_flutter_android/flutter/private/plugin_deps_guards.bzl b/rules_flutter_android/flutter/private/plugin_deps_guards.bzl index 929c64f5..fab10b5e 100644 --- a/rules_flutter_android/flutter/private/plugin_deps_guards.bzl +++ b/rules_flutter_android/flutter/private/plugin_deps_guards.bzl @@ -2,7 +2,7 @@ load("@bazel_skylib//lib:shell.bzl", "shell") load("@bazel_skylib//rules:build_test.bzl", "build_test") -load("@rules_flutter_core//flutter:defs.bzl", "pub_plugins_check") +load("@rules_flutter_core//flutter:composition.bzl", "pub_plugins_check") _UPDATE_SCRIPT = """#!/usr/bin/env bash set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash diff --git a/rules_flutter_android/flutter/private/plugins.bzl b/rules_flutter_android/flutter/private/plugins.bzl index 5624a3a1..324805b9 100644 --- a/rules_flutter_android/flutter/private/plugins.bzl +++ b/rules_flutter_android/flutter/private/plugins.bzl @@ -15,7 +15,7 @@ without an `android/` module. load(":abis.bzl", "MIN_SDK", "check_abis") load(":embedding.bzl", "ANNOTATION_JVM", "FLUTTER_EMBEDDING_ARTIFACTS", "TRANSITIVE_DEPS", "flutter_embedding_deps") load(":maven.bzl", "highest_versions", "maven_label") -load(":registrant.bzl", "android_plugin", "main_class_candidates", "render_android_registrant", "resolve_android_plugins", "supports_embedding_v2") +load(":registrant.bzl", "android_plugin", "main_class_candidates", "resolve_android_plugins", "supports_embedding_v2") visibility(["//flutter"]) @@ -59,9 +59,7 @@ _DEP_CONFIGURATIONS = [ _BUILD_LOADS = """# Generated by //flutter:extensions.bzl -- do not edit. load("@rules_android//rules:rules.bzl", "android_library") load("@rules_java//java:defs.bzl", "java_import", "java_library") -""" - -_PROJECT_BUILD_LOADS = """load("{maven_identity}", "flutter_maven_repository") +load("{composition}", "android_native_files", "flutter_build_config", "flutter_maven_repository", "flutter_native_files", "flutter_native_files_by_abi", "flutter_plugin_registrant") """ _MAVEN_TARGETS_TEMPLATE = """ @@ -102,10 +100,9 @@ java_library( _KOTLIN_BUILD_LOADS = """load("@rules_kotlin//kotlin:android.bzl", "kt_android_library") """ -# Loaded only into the BUILD of a plugin that actually has a CMake build, so a -# project with no native plugins never pulls rules_foreign_cc into analysis. +# Loaded only into plugin BUILD files that actually define a CMake build, so +# projects without native plugins never pull rules_foreign_cc into analysis. _NATIVE_BUILD_LOADS = """load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") -load("{native_strip}", "android_native_files") """ _BUILD_PACKAGE = """ @@ -114,21 +111,18 @@ package(default_visibility = ["//visibility:public"]) _PLUGIN_TEMPLATE = """ # {name}, built from the Gradle module the pub package ships. +flutter_build_config( + name = "build_config", + package_name = "{package}", + fields = {build_config_fields}, +) + {rule}( name = "{name}", - srcs = {srcs} + select({{ - "{mode_debug}": ["generated/debug/BuildConfig.java"], - "{mode_release}": ["generated/release/BuildConfig.java"], - }}), + srcs = {srcs} + [":build_config"], manifest = "android/src/main/AndroidManifest.xml", - # The plugin manifest carries more than a package name -- connectivity_plus - # declares ACCESS_NETWORK_STATE -- and it has to reach the APK, so it is - # exported up the dependency chain to android_binary rather than consumed - # locally. - # - # 1 rather than True for kt_android_library: it is built from rules_android's - # raw attrs, where exports_manifest is the underlying tri-state int, while the - # android_library *macro* accepts a bool and converts. + # Plugin permissions must reach android_binary through the manifest chain. + # kt_android_library uses integer booleans; android_library uses True/False. exports_manifest = {exports_manifest}, custom_package = "{package}", resource_files = {resource_files}, @@ -272,16 +266,11 @@ PACKAGE_INFO = struct( ) ''' -_NATIVE_LIBS_LOADS = """load("{native_files}", "flutter_native_files", "flutter_native_files_by_abi") -""" - _REGISTRANT_SRC_TEMPLATE = """ -filegroup( +flutter_plugin_registrant( name = "generated_plugin_registrant_src", - srcs = select({{ - "{debug}": ["{debug_src}"], - "{release}": ["{release_src}"], - }}), + plugins = {plugins}, + dev_plugins = {dev_plugins}, ) """ @@ -727,7 +716,7 @@ def _package_version(ctx, package_root, package_name): package_name, )) -def _build_config_sources(ctx, package_name, package_root, namespace, specs): +def _build_config_sources(ctx, package_name, package_root, specs): builtins = ["DEBUG", "BUILD_TYPE", "LIBRARY_PACKAGE_NAME"] seen = {} custom = [] @@ -770,20 +759,7 @@ def _build_config_sources(ctx, package_name, package_root, namespace, specs): name, _build_config_semantic(type_name, value, package_name, name), )) - custom_text = "".join(sorted(custom)) - sources = {} - for mode, debug, build_type in [("debug", "true", "debug"), ("release", "false", "release")]: - sources[mode] = ( - "package {};\n\n".format(namespace) + - "public final class BuildConfig {\n" + - " public static final boolean DEBUG = {};\n".format(debug) + - " public static final String BUILD_TYPE = \"{}\";\n".format(build_type) + - " public static final String LIBRARY_PACKAGE_NAME = {};\n".format(_java_string(namespace)) + - custom_text + - " private BuildConfig() {}\n" + - "}\n" - ) - return sources + return sorted(custom) def _extract_dependencies(build_gradle_text): """Return (coordinates, reasons) for one plugin's build.gradle. @@ -1396,30 +1372,27 @@ def _package_roots(ctx): return roots def _write_android_registrants(ctx, plugins, roots): - for mode in ["debug", "release"]: - entries = [] - for plugin in resolve_android_plugins(plugins, release = mode == "release"): - if plugin["plugin_class"] == None or plugin["package"] == None: - continue - name = plugin["name"] - root = ctx.path(roots[name]) - main_class = None - for candidate in main_class_candidates(plugin["package"], plugin["plugin_class"]): - path = root.get_child(*candidate.split("/")) - if path.exists: - main_class = path - break - if main_class == None: - fail("Plugin {} has no main class at {}.".format(name, ", ".join([ - str(root) + "/" + candidate - for candidate in main_class_candidates(plugin["package"], plugin["plugin_class"]) - ]))) - if supports_embedding_v2(ctx.read(main_class)): - entries.append((name, plugin["package"] + "." + plugin["plugin_class"])) - ctx.file( - "android/{}/GeneratedPluginRegistrant.java".format(mode), - render_android_registrant(entries), - ) + entries = {"plugins": [], "dev_plugins": []} + for plugin in resolve_android_plugins(plugins, release = False): + if plugin["plugin_class"] == None or plugin["package"] == None: + continue + name = plugin["name"] + root = ctx.path(roots[name]) + main_class = None + for candidate in main_class_candidates(plugin["package"], plugin["plugin_class"]): + path = root.get_child(*candidate.split("/")) + if path.exists: + main_class = path + break + if main_class == None: + fail("Plugin {} has no main class at {}.".format(name, ", ".join([ + str(root) + "/" + candidate + for candidate in main_class_candidates(plugin["package"], plugin["plugin_class"]) + ]))) + if supports_embedding_v2(ctx.read(main_class)): + key = "dev_plugins" if plugin.get("dev", False) else "plugins" + entries[key].append("{}|{}.{}".format(name, plugin["package"], plugin["plugin_class"])) + return entries def _write_maven_aliases(ctx, split, release_coordinates, all_coordinates): release_target_set = { @@ -1569,7 +1542,7 @@ def _flutter_plugins_impl(ctx): ) for p in plugins ] - _write_android_registrants(ctx, registrant_plugins, roots) + registrant = _write_android_registrants(ctx, registrant_plugins, roots) # Pub dependencies include entries without native Android targets, such as # Dart-only and default-package declarations. Recipes in the resolution @@ -1704,7 +1677,7 @@ def _flutter_plugins_impl(ctx): # the error names the plugin that actually caused it. ctx.file( "{}/BUILD.bazel".format(name), - _BUILD_LOADS + _BUILD_PACKAGE + _GATED_TEMPLATE.format( + _BUILD_LOADS.format(composition = str(Label("//flutter:composition.bzl"))) + _BUILD_PACKAGE + _GATED_TEMPLATE.format( name = name, reasons = ", ".join(gated), hint = ( @@ -1771,17 +1744,12 @@ def _flutter_plugins_impl(ctx): ), ) used_build_config_packages.append(name) - build_config_sources = _build_config_sources( + plugin_build_config_fields = _build_config_sources( ctx, name, package_root, - package, build_config_fields.get(name, []), ) - ctx.file("{}/generated/debug/BuildConfig.java".format(name), build_config_sources["debug"]) - ctx.file("{}/generated/release/BuildConfig.java".format(name), build_config_sources["release"]) - - # The native half, for plugins whose build.gradle drives CMake. native = "" native_deps = [] if "external_native_build" in reasons: @@ -1825,9 +1793,9 @@ def _flutter_plugins_impl(ctx): ctx.file( "{}/BUILD.bazel".format(name), - _BUILD_LOADS + + _BUILD_LOADS.format(composition = str(Label("//flutter:composition.bzl"))) + (_KOTLIN_BUILD_LOADS if kotlin_srcs else "") + - (_NATIVE_BUILD_LOADS.format(native_strip = ctx.attr.native_strip_bzl) if native else "") + + (_NATIVE_BUILD_LOADS if native else "") + _BUILD_PACKAGE + native + _PLUGIN_TEMPLATE.format( @@ -1838,6 +1806,7 @@ def _flutter_plugins_impl(ctx): srcs = repr(srcs), resource_files = repr(_list_files(root, "android/src/main/res", [])), package = package, + build_config_fields = repr(plugin_build_config_fields), deps = _plugin_dependency_expression( ctx, plugin_dependencies, @@ -1847,8 +1816,6 @@ def _flutter_plugins_impl(ctx): ctx.attr.maven_repo + "_debug", split, ), - mode_debug = ctx.attr.mode_debug, - mode_release = ctx.attr.mode_release, ), ) manifest.append({ @@ -2013,12 +1980,6 @@ def _flutter_plugins_impl(ctx): # contributes only a .so. dev = {p["name"]: p.get("dev_dependency", False) for p in plugins} - # The pub hub writes both registrants: the Dart one and the Java one per mode. - registrant = { - mode: "android/{}/GeneratedPluginRegistrant.java".format(mode) - for mode in ["debug", "release"] - } - # Preserve canonical Android manifest merge order; sort the Java edge below # so duplicate Java resources keep stable classpath precedence. embedding_deps = flutter_embedding_deps(maven_repo = ctx.attr.maven_repo) @@ -2026,10 +1987,7 @@ def _flutter_plugins_impl(ctx): ctx.file( "BUILD.bazel", - _BUILD_LOADS + - _PROJECT_BUILD_LOADS.format( - maven_identity = str(Label("//flutter/private:maven_identity.bzl")), - ) + + _BUILD_LOADS.format(composition = str(Label("//flutter:composition.bzl"))) + _MAVEN_TARGETS_TEMPLATE.format( maven_repo = ctx.attr.maven_repo, embedding_deps = _mode_selected_dependencies( @@ -2039,15 +1997,12 @@ def _flutter_plugins_impl(ctx): bool(dev_coordinates), ), ) + - _NATIVE_LIBS_LOADS.format( - native_files = str(Label("//flutter/private:native_files.bzl")), - ) + _BUILD_PACKAGE + _EMBEDDING_TEMPLATE.format( mode_debug = ctx.attr.mode_debug, mode_release = ctx.attr.mode_release, - embedding_debug = ctx.attr.embedding_debug, - embedding_release = ctx.attr.embedding_release, + embedding_debug = str(ctx.attr.embedding_debug), + embedding_release = str(ctx.attr.embedding_release), embedding_all = _mode_selected_dependencies( ctx, [":flutter_embedding_jar"] + sorted_embedding_deps, @@ -2067,10 +2022,8 @@ def _flutter_plugins_impl(ctx): ], dev, ctx), ) + _REGISTRANT_SRC_TEMPLATE.format( - debug = ctx.attr.mode_debug, - release = ctx.attr.mode_release, - debug_src = registrant["debug"], - release_src = registrant["release"], + plugins = repr(registrant["plugins"]), + dev_plugins = repr(registrant["dev_plugins"]), ) + _REGISTRANT_LIB_TEMPLATE.format( annotation_jvm = _mode_selected_dependencies( @@ -2109,10 +2062,6 @@ flutter_plugins = repository_rule( allow_single_file = True, mandatory = True, ), - "native_strip_bzl": attr.string( - doc = "Label of the internal ABI-pinned raw strip rule.", - mandatory = True, - ), "mode_debug": attr.string( doc = "Canonical label of Flutter's debug mode selector.", mandatory = True, @@ -2416,8 +2365,8 @@ def _flutter_plugins_ext_impl(ctx): flutter_plugins( name = project.name, abis = project.abis, - embedding_debug = Label("@flutter_embedding_debug//jar:file"), - embedding_release = Label("@flutter_embedding_release//jar:file"), + embedding_debug = Label("//flutter:embedding_jar_debug"), + embedding_release = Label("//flutter:embedding_jar_release"), metadata = project.metadata, package_config = project.package_config, coursier_options = project.coursier_options, @@ -2432,7 +2381,6 @@ def _flutter_plugins_ext_impl(ctx): for n, v in build_config_fields.get(project.name, {}).items() }, recipes = recipes.get(project.name, {}), - native_strip_bzl = str(Label("//flutter/private:native_strip.bzl")), mode_debug = str(Label("@rules_flutter_core//flutter:mode_debug")), mode_release = str(Label("@rules_flutter_core//flutter:mode_release")), ndk_source_properties = str(Label("@androidndk_cmake//:ndk_source_properties")), diff --git a/rules_flutter_android/tests/registrant_parity/BUILD.bazel b/rules_flutter_android/tests/registrant_parity/BUILD.bazel index 2635c4fa..bc11abbf 100644 --- a/rules_flutter_android/tests/registrant_parity/BUILD.bazel +++ b/rules_flutter_android/tests/registrant_parity/BUILD.bazel @@ -21,13 +21,14 @@ exports_files(glob(["*/app/pubspec.*"])) diff_test( name = "{}_{}_test".format(case, mode), file1 = "{}/expected/{}/GeneratedPluginRegistrant.java".format(case, mode), - file2 = "@registrant_parity_{}_android//:android/{}/GeneratedPluginRegistrant.java".format(case, mode), + file2 = "@registrant_parity_{}_android//:generated_plugin_registrant_src".format(case), + target_compatible_with = select({ + "@rules_flutter_core//flutter:mode_debug": [] if mode == "debug" else ["@platforms//:incompatible"], + "@rules_flutter_core//flutter:mode_release": [] if mode == "release" else ["@platforms//:incompatible"], + }), ) for case in CASES - for mode in [ - "debug", - "release", - ] + for mode in ("debug", "release") ] # What a plugin-free flutter_android_app compiles. diff --git a/rules_flutter_android/tools/ci/BUILD.bazel b/rules_flutter_android/tools/ci/BUILD.bazel index a7c3f6b7..704a08a7 100644 --- a/rules_flutter_android/tools/ci/BUILD.bazel +++ b/rules_flutter_android/tools/ci/BUILD.bazel @@ -9,15 +9,8 @@ genrule( name = "key_portability_check", srcs = [ "@rules_flutter_core//tools/ci:key_portability_test.py", - "//flutter:native_build_env.bzl", - "//flutter/private:android.bzl", - "//flutter/private:android_app.bzl", - "//flutter/private:android_inputs_rules.bzl", - "//flutter/private:native_strip.bzl", - "//flutter/private:apk_check.bzl", - "//flutter/private:ndk.bzl", - "//flutter/private:plugins.bzl", - "//flutter/private:integration_test_runner.bzl", + "//flutter:starlark_sources", + "//flutter/private:starlark_sources", ], outs = ["key_portability_check.checked"], cmd = """set -euo pipefail diff --git a/rules_flutter_core/MODULE.bazel b/rules_flutter_core/MODULE.bazel index 5ef1f129..19d771f5 100644 --- a/rules_flutter_core/MODULE.bazel +++ b/rules_flutter_core/MODULE.bazel @@ -33,8 +33,6 @@ test_metadata_parser_src( use_repo( flutter, - "flutter_embedding_debug", - "flutter_embedding_release", "flutter_engine_arm64_v8a_debug", "flutter_engine_arm64_v8a_release", "flutter_engine_armeabi_v7a_debug", diff --git a/rules_flutter_core/MODULE.bazel.lock b/rules_flutter_core/MODULE.bazel.lock index 69c968de..01e2bfdd 100644 --- a/rules_flutter_core/MODULE.bazel.lock +++ b/rules_flutter_core/MODULE.bazel.lock @@ -193,7 +193,7 @@ "moduleExtensions": { "//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "3eQ7CSujwGo7HWistYKJNKsA65Je6fZw/a3upweGNLo=", "recordedInputs": [ "REPO_MAPPING:,flutter_sdk +flutter+flutter_sdk", @@ -210,18 +210,6 @@ "repoRuleId": "@@//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/rules_flutter_core/flutter/BUILD.bazel b/rules_flutter_core/flutter/BUILD.bazel index 92d5abd0..ae4b4415 100644 --- a/rules_flutter_core/flutter/BUILD.bazel +++ b/rules_flutter_core/flutter/BUILD.bazel @@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"]) exports_files([ "defs.bzl", + "composition.bzl", "extensions.bzl", ]) @@ -48,3 +49,14 @@ config_setting( name = "mode_release", flag_values = {":mode": "release"}, ) + +# Host-side decoder for Flutter's device-test frame protocol. +filegroup( + name = "device_test_decoder", + srcs = [ + "//flutter/private/test/driver:device_decoder.dart", + "//flutter/private/test/driver:frame.dart", + "//flutter/private/test/driver:junit.dart", + "//flutter/private/test/driver:results.dart", + ], +) diff --git a/rules_flutter_core/flutter/abis.bzl b/rules_flutter_core/flutter/abis.bzl index 2f6199ce..0cab67d0 100644 --- a/rules_flutter_core/flutter/abis.bzl +++ b/rules_flutter_core/flutter/abis.bzl @@ -12,7 +12,6 @@ load( _ABI_PLATFORM_ATTRS = "ABI_PLATFORM_ATTRS", _MODES = "MODES", _abi_from_platform = "abi_from_platform", - _embedding_jar_label = "embedding_jar_label", _engine_jar_label = "engine_jar_label", ) @@ -32,6 +31,3 @@ abi_from_platform = _abi_from_platform # The prebuilt engine jar for one ABI and mode, as a Label. engine_jar_label = _engine_jar_label - -# The Java embedding jar for one mode, as a Label. -embedding_jar_label = _embedding_jar_label diff --git a/rules_flutter_core/flutter/composition.bzl b/rules_flutter_core/flutter/composition.bzl new file mode 100644 index 00000000..54d90e47 --- /dev/null +++ b/rules_flutter_core/flutter/composition.bzl @@ -0,0 +1,18 @@ +"""Public building blocks for hand-wired Flutter targets.""" + +load("//flutter/private:core.bzl", _FlutterArtifactInfo = "FlutterArtifactInfo") +load("//flutter/private:debug_core.bzl", _flutter_debug_core = "flutter_debug_core") +load("//flutter/private:integration_test.bzl", _FlutterIntegrationTestInfo = "FlutterIntegrationTestInfo") +load("//flutter/private:pubspec.bzl", _FlutterPubspecInfo = "FlutterPubspecInfo", _flutter_pubspec = "flutter_pubspec") +load("//flutter/private:rules.bzl", _FlutterNativeBuildEnvInfo = "FlutterNativeBuildEnvInfo", _dart_test_deps = "dart_test_deps", _flutter_assets = "flutter_assets", _pub_path_deps_check = "pub_path_deps_check", _pub_plugins_check = "pub_plugins_check") + +FlutterArtifactInfo = _FlutterArtifactInfo +FlutterPubspecInfo = _FlutterPubspecInfo +FlutterIntegrationTestInfo = _FlutterIntegrationTestInfo +FlutterNativeBuildEnvInfo = _FlutterNativeBuildEnvInfo +flutter_debug_core = _flutter_debug_core +flutter_pubspec = _flutter_pubspec +flutter_assets = _flutter_assets +pub_path_deps_check = _pub_path_deps_check +pub_plugins_check = _pub_plugins_check +dart_test_deps = _dart_test_deps diff --git a/rules_flutter_core/flutter/defs.bzl b/rules_flutter_core/flutter/defs.bzl index 27e98ec3..8e6bdf3e 100644 --- a/rules_flutter_core/flutter/defs.bzl +++ b/rules_flutter_core/flutter/defs.bzl @@ -1,79 +1,14 @@ -"""Supported BUILD-file API for Flutter Consumer Modules. +"""Supported convenience BUILD API for Flutter consumer modules.""" -Consumer BUILD files load this one entrypoint rather than the implementation -under ``//flutter/private``. Every name here is either written by hand in a -Consumer Module or named by a BUILD file these rules generate; nothing is -exported merely because it exists. Rules composed by the macros below -- -``dart_aot_kernel``, ``dart_jit_kernel`` and ``dart_test_kernel`` stay private, -so their attributes remain free to change without a consumer migration. The -ABI names are public through ``@rules_flutter_core//flutter:abis.bzl``, which -re-exports the consumer-facing subset. +load("//flutter/private:core.bzl", _flutter_core = "flutter_core") +load("//flutter/private:flutter_test.bzl", _flutter_test = "flutter_test", _flutter_test_suite = "flutter_test_suite") +load("//flutter/private:integration_test.bzl", _flutter_integration_test = "flutter_integration_test") +load("//flutter/private:pubspec.bzl", _flutter_pubspec = "flutter_pubspec") +load("//flutter/private:rules.bzl", _flutter_package = "flutter_package") -The Android macros load from `@rules_flutter_android//flutter:defs.bzl`. -""" - -load( - "//flutter/private:core.bzl", - _FlutterArtifactInfo = "FlutterArtifactInfo", - _flutter_core = "flutter_core", -) -load( - "//flutter/private:debug_core.bzl", - _flutter_debug_core = "flutter_debug_core", -) -load( - "//flutter/private:flutter_test.bzl", - _flutter_test = "flutter_test", - _flutter_test_suite = "flutter_test_suite", -) -load( - "//flutter/private:integration_test.bzl", - _FlutterIntegrationTestInfo = "FlutterIntegrationTestInfo", - _flutter_integration_test = "flutter_integration_test", -) -load("//flutter/private:pubspec.bzl", _FlutterPubspecInfo = "FlutterPubspecInfo", _flutter_pubspec = "flutter_pubspec") -load( - "//flutter/private:rules.bzl", - _FlutterNativeBuildEnvInfo = "FlutterNativeBuildEnvInfo", - _dart_test_deps = "dart_test_deps", - _flutter_assets = "flutter_assets", - _flutter_package = "flutter_package", - _pub_path_deps_check = "pub_path_deps_check", - _pub_plugins_check = "pub_plugins_check", -) - -# Explicit assignments make the curated names exports of this module. Merely -# importing names with load() does not re-export them to downstream BUILD files. -# -# Written by hand in a Consumer Module. +flutter_core = _flutter_core flutter_package = _flutter_package flutter_test = _flutter_test flutter_test_suite = _flutter_test_suite - -# Named by the BUILD files the pub hub generates, so they are loaded across a -# repository boundary and have to resolve from this entrypoint. -dart_test_deps = _dart_test_deps - -# The package-level build rules. -flutter_pubspec = _flutter_pubspec -flutter_assets = _flutter_assets -pub_path_deps_check = _pub_path_deps_check -pub_plugins_check = _pub_plugins_check - -# The platform-composed artifact. An Android binary consumes it through -# rules_flutter_android's flutter_android_inputs. -flutter_core = _flutter_core - -FlutterArtifactInfo = _FlutterArtifactInfo - -# A debug artifact of an app's core, compiled from another entrypoint. -flutter_debug_core = _flutter_debug_core flutter_integration_test = _flutter_integration_test -FlutterIntegrationTestInfo = _FlutterIntegrationTestInfo - -# Read by rules_flutter_android for the version an APK declares. -FlutterPubspecInfo = _FlutterPubspecInfo - -# What a native build environment rule returns, for the `native_build_env` -# attribute of flutter_core and flutter_assets. -FlutterNativeBuildEnvInfo = _FlutterNativeBuildEnvInfo +flutter_pubspec = _flutter_pubspec diff --git a/rules_flutter_core/flutter/private/BUILD.bazel b/rules_flutter_core/flutter/private/BUILD.bazel index 1c128ca9..f9a0ac2b 100644 --- a/rules_flutter_core/flutter/private/BUILD.bazel +++ b/rules_flutter_core/flutter/private/BUILD.bazel @@ -1,7 +1,7 @@ """Implementation of the Flutter rules. -Nothing here is supported API: Consumer Modules load //flutter:defs.bzl and -//flutter:extensions.bzl. The `.bzl` files beside this file declare +Nothing here is supported API: consumers load //flutter:defs.bzl and +//flutter:extensions.bzl; hand-wired targets load //flutter:composition.bzl. The `.bzl` files beside this file declare `visibility(["//flutter"])`, except `toolchain_repo.bzl`, whose `//` visibility allows it to be loaded from the ruleset's root MODULE.bazel only, and `hook_packages.bzl`, whose public visibility allows generated pub hub @@ -191,15 +191,3 @@ config_setting( flag_values = {"//flutter:mode": "debug"}, visibility = ["//visibility:public"], ) - -# Source closure imported by the Android host runner's compiled Dart kernel. -filegroup( - name = "device_decoder_library", - srcs = [ - "//flutter/private/test/driver:device_decoder.dart", - "//flutter/private/test/driver:frame.dart", - "//flutter/private/test/driver:junit.dart", - "//flutter/private/test/driver:results.dart", - ], - visibility = ["//visibility:public"], -) diff --git a/rules_flutter_core/flutter/private/abis.bzl b/rules_flutter_core/flutter/private/abis.bzl index dc0719b9..e595f78d 100644 --- a/rules_flutter_core/flutter/private/abis.bzl +++ b/rules_flutter_core/flutter/private/abis.bzl @@ -177,36 +177,6 @@ def engine_libs_label(abi, mode): """ return Label("@{}//jar:libs".format(engine_repo(abi, mode))) -def embedding_repo(mode): - """Name of the repository holding the Java embedding jar for one mode. - - Architecture-independent -- unlike engine_repo, there is no ABI axis -- - but mode-keyed for the same reason: `BuildConfig.DEBUG` is a - `static final boolean`, so the jar a `MainActivity` compiles against fixes - the mode, and only the debug jar's carries service-protocol/JIT support. - - Args: - mode: "release" or "debug". - - Returns: - A repository name. - """ - return "flutter_embedding_" + mode - -def embedding_jar_label(mode): - """The Java embedding jar for one mode, as a Label. - - A `Label`, not a string, for the same repository-mapping reason as - engine_jar_label: the repository belongs to this module. - - Args: - mode: "release" or "debug". - - Returns: - A Label for the unstripped embedding jar. - """ - return Label("@{}//jar:file".format(embedding_repo(mode))) - def aot_target_compatible_with(): """target_compatible_with for a rule that only runs under an AOT-capable mode. diff --git a/rules_flutter_core/flutter/private/core.bzl b/rules_flutter_core/flutter/private/core.bzl index 26183833..13982876 100644 --- a/rules_flutter_core/flutter/private/core.bzl +++ b/rules_flutter_core/flutter/private/core.bzl @@ -13,7 +13,7 @@ The android_binary side takes the artifact through `FlutterArtifactInfo`. See load("@bazel_skylib//lib:shell.bzl", "shell") load("@bazel_skylib//rules:build_test.bzl", "build_test") load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") -load(":abis.bzl", "ABIS", "ABI_PLATFORM_ATTRS", "MODES", "abi_from_platform", "aot_target_compatible_with", "embedding_jar_label", "engine_libs_label") +load(":abis.bzl", "ABIS", "ABI_PLATFORM_ATTRS", "MODES", "abi_from_platform", "aot_target_compatible_with", "engine_libs_label") load(":pubspec.bzl", "FlutterPubspecInfo", "flutter_pubspec") load(":rules.bzl", "dart_aot_elf", "dart_aot_kernel", "dart_jit_kernel", "flutter_assets", "flutter_package", "flutter_test_assets", "lib_root", "library_path", "pub_path_deps_check", "pub_plugins_check") @@ -54,7 +54,6 @@ FlutterArtifactInfo = provider( "abi": "The Android ABI of this split, or None on a host build.", "libapp": "File: the libapp.so of this ABI, or None under debug.", "engine_libs": "list of File: this ABI's engine .so files, unstripped.", - "embedding_jar": "File: the Java embedding jar for this mode.", "assets": "Directory: the flutter_assets bundle, with the debug overlay under debug.", }, ) @@ -192,7 +191,6 @@ def _flutter_core_impl(ctx): abi = abi_from_platform(ctx) libapp = ctx.files.libapp engine_libs = ctx.files.engine_libs - embedding_jar = ctx.file.embedding assets = ctx.attr.assets[DefaultInfo].files.to_list() (mode_free_assets,) = ctx.files.mode_free_assets if len(assets) != 1: @@ -206,19 +204,17 @@ def _flutter_core_impl(ctx): fail("{}: release ABI '{}' needs one libapp.so".format(ctx.label, abi)) return [ - DefaultInfo(files = depset(libapp + engine_libs + [embedding_jar, assets[0]])), + DefaultInfo(files = depset(libapp + engine_libs + [assets[0]])), OutputGroupInfo( flutter_assets = depset([assets[0]]), flutter_engine = depset(engine_libs), flutter_libapp = depset(libapp), - flutter_embedding = depset([embedding_jar]), ), FlutterArtifactInfo( mode = mode, abi = abi, libapp = libapp[0] if libapp else None, engine_libs = engine_libs, - embedding_jar = embedding_jar, assets = assets[0], ), FlutterCoreInfo( @@ -241,7 +237,6 @@ _flutter_core = rule( { "libapp": attr.label_list(allow_files = True, doc = "The dart_aot_elf of this split's ABI; empty under debug and on a host build."), "engine_libs": attr.label_list(allow_files = True, doc = "This split's engine .so files; empty on a host build."), - "embedding": attr.label(allow_single_file = True, mandatory = True), "assets": attr.label(mandatory = True, doc = "The platform-neutral bundle."), "mode_free_assets": attr.label(allow_files = True, mandatory = True, cfg = _platform_neutral_transition, doc = "The flutter_assets tree before any debug overlay, built under the host platform. A flutter_debug_core overlays its own kernel on it, so the app's assets label is never named outside this package."), "pubspec": attr.label(mandatory = True, providers = [FlutterPubspecInfo]), @@ -455,10 +450,6 @@ def flutter_core( [(_split_label(abi, mode), [engine_libs_label(abi, mode)]) for abi in ABIS for mode in MODES] + [("//conditions:default", [])], )), - embedding = select({ - Label("//flutter:mode_release"): embedding_jar_label("release"), - Label("//flutter:mode_debug"): embedding_jar_label("debug"), - }), assets = ":" + name + "_bundle", mode_free_assets = ":" + name + "_assets", pubspec = ":pubspec", diff --git a/rules_flutter_core/flutter/private/debug_core.bzl b/rules_flutter_core/flutter/private/debug_core.bzl index b11c3b8f..4677a3db 100644 --- a/rules_flutter_core/flutter/private/debug_core.bzl +++ b/rules_flutter_core/flutter/private/debug_core.bzl @@ -102,22 +102,20 @@ def _flutter_debug_core_impl(ctx): artifact = ctx.attr.app[FlutterArtifactInfo] (assets,) = ctx.files.bundle - # No libapp: a debug artifact runs its kernel from the bundle. The engine and - # embedding are the app's own, selected by the same configuration. + # No libapp: a debug artifact runs its kernel from the bundle. The engine is + # selected by the same configuration as the app. return [ - DefaultInfo(files = depset(artifact.engine_libs + [artifact.embedding_jar, assets])), + DefaultInfo(files = depset(artifact.engine_libs + [assets])), OutputGroupInfo( flutter_assets = depset([assets]), flutter_engine = depset(artifact.engine_libs), flutter_libapp = depset([]), - flutter_embedding = depset([artifact.embedding_jar]), ), FlutterArtifactInfo( mode = "debug", abi = artifact.abi, libapp = None, engine_libs = artifact.engine_libs, - embedding_jar = artifact.embedding_jar, assets = assets, ), ctx.attr.app[FlutterPubspecInfo], diff --git a/rules_flutter_core/flutter/private/pub_lock.bzl b/rules_flutter_core/flutter/private/pub_lock.bzl index 0bd92762..fd86e060 100644 --- a/rules_flutter_core/flutter/private/pub_lock.bzl +++ b/rules_flutter_core/flutter/private/pub_lock.bzl @@ -168,8 +168,6 @@ def _plugins_metadata(entries, root_name, deps, reached): "plugin_class": info.get("pluginClass"), }) plugins[platform] = plugin_list - if "android" not in plugins: - plugins["android"] = [] return {"plugins": plugins} def _pub_hub_impl(rctx): @@ -361,7 +359,7 @@ def _pub_hub_impl(rctx): } package_dirs_src = ", ".join(["{}: {}".format(repr(name), repr(path)) for name, path in package_dirs.items()]) - rctx.file("BUILD.bazel", """load("{defs_bzl}", "dart_test_deps") + rctx.file("BUILD.bazel", """load("{composition_bzl}", "dart_test_deps") load("{hook_packages_bzl}", "flutter_hook_packages") package(default_visibility = ["//visibility:public"]) @@ -397,7 +395,7 @@ dart_test_deps( pub_srcs = [":lib"], ) """.format( - defs_bzl = rctx.attr.defs_bzl, + composition_bzl = rctx.attr.composition_bzl, hook_packages_bzl = rctx.attr.hook_packages_bzl, package_dirs = package_dirs_src, hub = rctx.attr.name, @@ -426,7 +424,7 @@ pub_hub = repository_rule( doc = "Packages left out of the test deps kernel. A test that imports one compiles it itself; see dart_test_deps.", ), "lock_name": attr.string(mandatory = True, doc = "The hub's name in pub.lock, for messages."), - "defs_bzl": attr.string(mandatory = True, doc = "The label of defs.bzl, which the generated BUILD file loads dart_test_deps from."), + "composition_bzl": attr.string(mandatory = True, doc = "The label of composition.bzl, which the generated BUILD file loads dart_test_deps from."), "hook_packages_bzl": attr.string(mandatory = True, doc = "Label loaded by generated hub BUILD files for private package metadata."), }, ) @@ -468,7 +466,7 @@ def _pub_impl(mctx): sdk = [package["name"] for package in resolved["sdk"]], test_deps_exclude = lock.test_deps_exclude, lock_name = lock.name, - defs_bzl = str(Label("//flutter:defs.bzl")), + composition_bzl = str(Label("//flutter:composition.bzl")), hook_packages_bzl = str(Label("//flutter/private:hook_packages.bzl")), ) diff --git a/rules_flutter_core/flutter/private/pubspec.bzl b/rules_flutter_core/flutter/private/pubspec.bzl index 0d33a9c2..51bb6b26 100644 --- a/rules_flutter_core/flutter/private/pubspec.bzl +++ b/rules_flutter_core/flutter/private/pubspec.bzl @@ -1,18 +1,8 @@ """The app's pubspec, as one target every other rule reads its facts from. -pubspec.yaml holds several facts the build needs -- the package name that makes -a `package:` URI resolvable, the version Android publishes as versionCode and -versionName, and the asset and font declarations `flutter build bundle` reads. -Before this they were spelled out again in BUILD files, where a rename in the -pubspec left them stale and the failure was silent: a mismatched package URI -compiles in and changes nothing. - -So a consumer names the pubspec once, and the facts travel by provider. - -**One fact per file, deliberately.** Downstream actions depend on the file -holding the fact they use, not on pubspec.yaml, so a version bump does not -invalidate the kernel -- Bazel keys an action on the content of its inputs, and -the package-name file is unchanged. +One fact per file, deliberately: consumers depend only on the fact they use. +The package name, build name and build number travel by provider; asset and +font declarations remain inputs to `flutter build bundle`. """ visibility(["//flutter"]) @@ -22,15 +12,15 @@ FlutterPubspecInfo = provider( fields = { "src": "The pubspec.yaml itself, for tools that parse it in full.", "package_name": "File holding the Dart package name.", - "version_name": "File holding the version's name part.", - "version_code": "File holding the version's build number.", + "build_name": "File holding Flutter's build name.", + "build_number": "File holding Flutter's build number.", }, ) def _flutter_pubspec_impl(ctx): name = ctx.actions.declare_file(ctx.label.name + "/package_name") - version_name = ctx.actions.declare_file(ctx.label.name + "/version_name") - version_code = ctx.actions.declare_file(ctx.label.name + "/version_code") + build_name = ctx.actions.declare_file(ctx.label.name + "/build_name") + build_number = ctx.actions.declare_file(ctx.label.name + "/build_number") args = ctx.actions.args() @@ -38,8 +28,8 @@ def _flutter_pubspec_impl(ctx): args.add(ctx.file._reader) args.add("--pubspec", ctx.file.src) args.add("--name-out", name) - args.add("--version-name-out", version_name) - args.add("--version-code-out", version_code) + args.add("--build-name-out", build_name) + args.add("--build-number-out", build_number) # No env: this action reads one file and writes three, so its key carries no # machine-specific path and it can be served from a shared cache. @@ -47,18 +37,18 @@ def _flutter_pubspec_impl(ctx): command = 'exec python3 "$@"', arguments = [args], inputs = [ctx.file.src, ctx.file._reader], - outputs = [name, version_name, version_code], + outputs = [name, build_name, build_number], mnemonic = "FlutterPubspec", progress_message = "Reading %{label}", ) return [ - DefaultInfo(files = depset([name, version_name, version_code])), + DefaultInfo(files = depset([name, build_name, build_number])), FlutterPubspecInfo( src = ctx.file.src, package_name = name, - version_name = version_name, - version_code = version_code, + build_name = build_name, + build_number = build_number, ), ] diff --git a/rules_flutter_core/flutter/private/read_pubspec.py b/rules_flutter_core/flutter/private/read_pubspec.py index e1fe79be..f1a6915f 100644 --- a/rules_flutter_core/flutter/private/read_pubspec.py +++ b/rules_flutter_core/flutter/private/read_pubspec.py @@ -45,21 +45,20 @@ def package_name(path): def version(path): - """`version:` as (name, code).""" + """`version:` as (build_name, build_number).""" value = top_level(path, "version") if not value: fail("{}: no top-level `version:` key.".format(path)) name, sep, code = value.partition("+") if not name: - fail("{}: version '{}' has no name part.".format(path, value)) + fail("{}: version '{}' has no build name.".format(path, value)) # Flutter's own default when the build number is omitted. code = code if sep else "1" if not code.isdigit(): fail( - "{}: build number '{}' is not an integer. Android's versionCode " - "must be one.".format(path, code), + "{}: build number '{}' is not an integer.".format(path, code), ) return name, code @@ -73,14 +72,14 @@ def main(): parser = argparse.ArgumentParser(description = __doc__) parser.add_argument("--pubspec", required = True) parser.add_argument("--name-out", required = True) - parser.add_argument("--version-name-out", required = True) - parser.add_argument("--version-code-out", required = True) + parser.add_argument("--build-name-out", required = True) + parser.add_argument("--build-number-out", required = True) args = parser.parse_args() - version_name, version_code = version(args.pubspec) + build_name, build_number = version(args.pubspec) write(args.name_out, package_name(args.pubspec)) - write(args.version_name_out, version_name) - write(args.version_code_out, version_code) + write(args.build_name_out, build_name) + write(args.build_number_out, build_number) if __name__ == "__main__": diff --git a/rules_flutter_core/flutter/private/repo.bzl b/rules_flutter_core/flutter/private/repo.bzl index c0eb349a..22acc042 100644 --- a/rules_flutter_core/flutter/private/repo.bzl +++ b/rules_flutter_core/flutter/private/repo.bzl @@ -7,7 +7,7 @@ artifacts are fetched lazily by `flutter precache`, so a fully hermetic download rule is a separate concern. See README for that tradeoff. """ -load(":abis.bzl", "ABIS", "AOT_MODES", "MODES", "embedding_repo", "engine_repo") +load(":abis.bzl", "ABIS", "AOT_MODES", "MODES", "engine_repo") load(":sdk_packages.bzl", "sdk_package_root") visibility(["//flutter"]) @@ -428,13 +428,6 @@ def _flutter_impl(ctx): flutter_tools_packages(name = "flutter_tools_packages") - # Each artifact repository reads the version file through the SDK label. - # The label dependency makes Bazel re-resolve these URLs with that SDK. - for mode in MODES: - flutter_engine_jar( - name = embedding_repo(mode), - artifact = "flutter_embedding_" + mode, - ) for abi, info in ABIS.items(): for mode in MODES: flutter_engine_jar( diff --git a/rules_flutter_core/tools/ci/key_portability_test.py b/rules_flutter_core/tools/ci/key_portability_test.py index 4acfb5ec..f47da373 100755 --- a/rules_flutter_core/tools/ci/key_portability_test.py +++ b/rules_flutter_core/tools/ci/key_portability_test.py @@ -26,10 +26,12 @@ "native_build_env": "flutter/native_build_env.bzl", "native_strip": "flutter/private/native_strip.bzl", "android_integration_test": "flutter/private/integration_test_runner.bzl", + "composition": "flutter/composition.bzl", } # Owned by rules_flutter_android: checked only when --android-root names that module. -ANDROID_FILES = frozenset(("android", "plugins", "ndk", "android_inputs", "apk_check", "native_build_env", "native_strip", "android_integration_test")) +ANDROID_FILES = frozenset(("android", "plugins", "ndk", "android_inputs", "apk_check", "native_build_env", "native_strip", "android_integration_test", "composition")) +_CORE_PRIVATE_LABEL = re.compile(r"@@?rules_flutter_core[^/]*//flutter/private(?=[:/\"])") class Checker: @@ -82,6 +84,7 @@ def run(self): self.check_d() self.check_e() self.check_f() + self.check_u() self.check_g() self.check_h() self.check_i() @@ -96,8 +99,35 @@ def run(self): self.check_r() self.check_s() self.check_t() + self.check_android_private() return self.failures + def check_android_private(self): + if self.android_root is None: + return + root = self.android_root / "flutter" + if not root.is_dir(): + self.fail("T", "Android Flutter source root is missing: {}".format(root)) + return + files = sorted( + path + for path in root.rglob("*") + if path.is_file() and (path.suffix == ".bzl" or path.name.startswith("BUILD")) + ) + if not files: + self.fail("T", "Android Flutter source root has no Starlark or BUILD files: {}".format(root)) + return + for path in files: + try: + source = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as error: + self.fail("T", "{} cannot be read: {}".format(path.relative_to(self.android_root), error)) + continue + for line_number, line in enumerate(source.splitlines(), 1): + if _CORE_PRIVATE_LABEL.search(line): + relative = path.relative_to(self.android_root) + self.fail("T", "{}:{} references a core private target".format(relative, line_number)) + def tree(self, name, contract): if not self._requested(name): return None @@ -494,6 +524,62 @@ def check_f(self): if rule is None: self.fail("F", "plugins.bzl must define flutter_plugins with repository_rule") + def check_u(self): + tree = self.tree("composition", "U") + if tree is None: + return + for rule_name, implementation_name, output_name in ( + ("flutter_plugin_registrant", "_registrant_impl", "GeneratedPluginRegistrant.java"), + ("flutter_build_config", "_build_config_impl", "BuildConfig.java"), + ): + rule = _rule_decl(tree, rule_name) + if rule is None or not _rule_has_attr(tree, rule_name, "_mode"): + self.fail("U", "{} must be a mode-aware public rule".format(rule_name)) + continue + implementation = _function(tree, implementation_name) + if implementation is None: + self.fail("U", "{} must define {}".format(rule_name, implementation_name)) + continue + declaration = _action_call(implementation, "declare_file") + declaration_path = declaration.args[0] if declaration and declaration.args else None + if ( + not isinstance(declaration_path, ast.BinOp) + or not isinstance(declaration_path.op, ast.Add) + or not _contains_attr(declaration_path, ["ctx", "label", "name"]) + or not _contains_string_part(declaration_path, output_name) + ): + self.fail("U", "{} must declare a target-scoped Java output ending in {}".format(rule_name, output_name)) + output_name_value = next( + ( + _name(target) + for node in ast.walk(implementation) + if isinstance(node, ast.Assign) and node.value is declaration + for target in node.targets + if isinstance(target, ast.Name) + ), + None, + ) + default_info = next( + ( + node + for node in ast.walk(implementation) + if isinstance(node, ast.Call) and _call_path(node) == ["DefaultInfo"] + ), + None, + ) + files = _keyword(default_info, "files") + depset = files if isinstance(files, ast.Call) and _call_path(files) == ["depset"] else None + output_list = depset.args[0] if depset and depset.args else None + if ( + output_name_value is None + or not isinstance(output_list, ast.List) + or len(output_list.elts) != 1 + or _name(output_list.elts[0]) != output_name_value + ): + self.fail("U", "{} must expose only its generated Java file through DefaultInfo".format(rule_name)) + if _action_call(implementation, "write") is None: + self.fail("U", "{} must generate Java through ctx.actions.write".format(rule_name)) + def check_g(self): tree = self.tree("ndk", "G") if tree is None: @@ -589,7 +675,7 @@ def check_g(self): def check_h(self): regions = [] - for name in ("defs", "android", "plugins", "core", "android_inputs", "apk_check", "device_test"): + for name in ("defs", "android", "plugins", "core", "android_inputs", "apk_check", "device_test", "composition"): tree = self.trees.get(name) if tree is None: continue @@ -1124,6 +1210,7 @@ def _file_contract(name): "dart_registrant": "N", "pub_lock": "O", "android_integration_test": "T", + "composition": "U", }[name] @@ -1419,7 +1506,18 @@ def _action_regions(name, tree): return regions +def _private_label_selftest(): + cases = ( + '@rules_flutter_core//flutter/private:target', + '@rules_flutter_core//flutter/private/subpackage:target', + '@@rules_flutter_core+//flutter/private:target', + ) + if not all(_CORE_PRIVATE_LABEL.search(case) for case in cases): + raise AssertionError("core-private label matcher missed a supported label spelling") + + def main(argv=None): + _private_label_selftest() parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, default=None, help="core module root; enables the core contracts") parser.add_argument("--android-root", type=Path, default=None, help="rules_flutter_android root; enables the Android contracts") @@ -1430,7 +1528,7 @@ def main(argv=None): if failures: print("\n".join(failures), file=sys.stderr) return 1 - print("OK: key portability contracts A-T hold") + print("OK: key portability contracts A-U hold") return 0 diff --git a/tests/consumer/BUILD.bazel b/tests/consumer/BUILD.bazel index 3a33d94a..9daa89a4 100644 --- a/tests/consumer/BUILD.bazel +++ b/tests/consumer/BUILD.bazel @@ -1,24 +1,20 @@ """The supported BUILD API for a Consumer Module. -Core rules load from @rules_flutter_core//flutter:defs.bzl. Android macros load from @rules_flutter_android//flutter:defs.bzl. +Core rules load from @rules_flutter_core//flutter:defs.bzl. Hand-wired core +targets may also load composition.bzl; Android macros load from +@rules_flutter_android//flutter:defs.bzl, with hand-wired rules in composition.bzl. """ load("@bazel_skylib//rules:build_test.bzl", "build_test") -load("@rules_flutter_android//flutter:defs.bzl", "flutter_android_app", "flutter_android_inputs", "flutter_assets", "flutter_native_contribution", "flutter_plugin_deps_guards") -load( - "@rules_flutter_core//flutter:defs.bzl", - "flutter_core", - "flutter_package", - "flutter_pubspec", - "flutter_test", - "pub_path_deps_check", -) +load("@rules_flutter_android//flutter:composition.bzl", "flutter_android_inputs", "flutter_embedding_deps") +load("@rules_flutter_android//flutter:defs.bzl", "flutter_android_app", "flutter_assets", "flutter_native_contribution", "flutter_plugin_deps_guards") +load("@rules_flutter_core//flutter:composition.bzl", "pub_path_deps_check") +load("@rules_flutter_core//flutter:defs.bzl", "flutter_core", "flutter_package", "flutter_pubspec", "flutter_test") load("@rules_java//java:defs.bzl", "java_binary", "java_import") load( "@rules_kotlin//kotlin:android.bzl", "kt_android_library", ) -load(":embedding_deps.bzl", "EMBEDDING_DEPS") package(default_visibility = ["//visibility:private"]) @@ -367,11 +363,8 @@ build_test( java_import( name = "build_config_runtime_embedding", - jars = select({ - "@rules_flutter_core//flutter:mode_debug": ["@flutter_embedding_debug//jar:file"], - "@rules_flutter_core//flutter:mode_release": ["@flutter_embedding_release//jar:file"], - }), - deps = EMBEDDING_DEPS, + jars = ["@rules_flutter_android//flutter:embedding_jar"], + deps = flutter_embedding_deps(), ) java_binary( diff --git a/tests/consumer/MODULE.bazel b/tests/consumer/MODULE.bazel index a1af9fcf..4e67c2b5 100644 --- a/tests/consumer/MODULE.bazel +++ b/tests/consumer/MODULE.bazel @@ -19,7 +19,7 @@ local_path_override( flutter = use_extension("@rules_flutter_core//flutter:extensions.bzl", "flutter") flutter.sdk(version = "3.47.3") -use_repo(flutter, "flutter_embedding_debug", "flutter_embedding_release") +use_repo(flutter, "flutter_sdk") # Separate pub hubs for the root fixtures, the standard-layout app and the # composed app. @@ -36,6 +36,64 @@ pub.lock( name = "composed_pub", lock = "//composed_app:pubspec.lock", ) -use_repo(pub, "composed_pub", "pub", "standard_layout_pub") +pub.lock( + name = "hand_wired_pub", + lock = "//hand_wired_app:pubspec.lock", +) +use_repo(pub, "composed_pub", "hand_wired_pub", "pub", "standard_layout_pub") include("//android:config.MODULE.bazel") + +hand_wired_maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +hand_wired_maven.install( + name = "hand_wired_release", + artifacts = [ + "androidx.lifecycle:lifecycle-common:2.7.0", + "androidx.lifecycle:lifecycle-common-java8:2.7.0", + "androidx.lifecycle:lifecycle-process:2.7.0", + "androidx.lifecycle:lifecycle-runtime:2.7.0", + "androidx.fragment:fragment:1.7.1", + "androidx.annotation:annotation:1.8.1", + "androidx.tracing:tracing:1.2.0", + "androidx.core:core:1.13.1", + "androidx.window:window-java:1.2.0", + "androidx.window:window:1.2.0", + "androidx.exifinterface:exifinterface:1.4.1", + "com.getkeepsafe.relinker:relinker:1.4.5", + "com.google.code.gson:gson:2.11.0", + "androidx.annotation:annotation-jvm:1.8.1", + ], + repositories = [ + "https://maven.google.com", + "https://maven-central.storage-download.googleapis.com/maven2", + "https://repo1.maven.org/maven2", + ], + version_conflict_policy = "pinned", +) +hand_wired_maven.install( + name = "hand_wired_debug", + artifacts = [ + "androidx.lifecycle:lifecycle-common:2.7.0", + "androidx.lifecycle:lifecycle-common-java8:2.7.0", + "androidx.lifecycle:lifecycle-process:2.7.0", + "androidx.lifecycle:lifecycle-runtime:2.7.0", + "androidx.fragment:fragment:1.7.1", + "androidx.annotation:annotation:1.8.1", + "androidx.tracing:tracing:1.2.0", + "androidx.core:core:1.13.1", + "androidx.window:window-java:1.2.0", + "androidx.window:window:1.2.0", + "androidx.exifinterface:exifinterface:1.4.1", + "com.getkeepsafe.relinker:relinker:1.4.5", + "com.google.code.gson:gson:2.11.0", + "org.apache.commons:commons-lang3:3.17.0", + "androidx.annotation:annotation-jvm:1.8.1", + ], + repositories = [ + "https://maven.google.com", + "https://maven-central.storage-download.googleapis.com/maven2", + "https://repo1.maven.org/maven2", + ], + version_conflict_policy = "pinned", +) +use_repo(hand_wired_maven, "hand_wired_debug", "hand_wired_release") diff --git a/tests/consumer/MODULE.bazel.lock b/tests/consumer/MODULE.bazel.lock index d6a952d4..dba0728b 100644 --- a/tests/consumer/MODULE.bazel.lock +++ b/tests/consumer/MODULE.bazel.lock @@ -408,7 +408,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "AFQI5PN97mnks2yWgIX361TV8306rJ4VPuWmhG1yFNA=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -430,7 +430,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "MIZKH8xcfb1pscqq81jQhHF4ylGO08PRxzsEQhM4UcY=", "recordedInputs": [ "REPO_MAPPING:,composed_pub sameri11_rules_flutter_core++pub+composed_pub", @@ -441,8 +441,6 @@ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:sameri11_rules_flutter_android+,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:sameri11_rules_flutter_android+,androidndk_cmake sameri11_rules_flutter_android++android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -452,8 +450,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@+namespace_plugin_metadata+namespace_plugin_metadata//:plugins_metadata.json", "package_config": "@@+namespace_plugin_metadata+namespace_plugin_metadata//:package_config.json", "coursier_options": [], @@ -476,7 +474,6 @@ "absent_package": "{\"bzl\":\"@@external_app+//:absent_package.bzl\",\"macro\":\"absent_package_recipe\",\"root\":false}", "recipe_plugin": "{\"bzl\":\"@@//:recipe_fixture.bzl\",\"macro\":\"recipe_fixture\",\"root\":true}" }, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -490,8 +487,8 @@ "x86_64", "armeabi-v7a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+composed_pub//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+composed_pub//:package_config.json", "coursier_options": [], @@ -505,7 +502,6 @@ "recipes": { "recipe_package": "{\"bzl\":\"@@//composed_app:recipe_package.bzl\",\"macro\":\"recipe_package_recipe\",\"root\":true}" }, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -519,8 +515,8 @@ "x86_64", "armeabi-v7a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@sameri11_rules_flutter_core++pub+composed_pub//:plugins_metadata.json", "package_config": "@@sameri11_rules_flutter_core++pub+composed_pub//:package_config.json", "coursier_options": [], @@ -535,7 +531,6 @@ "recipe_package": "{\"bzl\":\"@@//composed_app:recipe_package.bzl\",\"macro\":\"recipe_package_recipe\",\"root\":true}", "cmake_plugin": "{\"bzl\":\"@@//composed_app:recipe_package.bzl\",\"macro\":\"cmake_override_recipe\",\"root\":true}" }, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -544,9 +539,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "3YgLOQ5KctGblzmwvkmzbmE3OR8fATY6yGn0owwy7n8=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -563,18 +581,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/tests/consumer/README.md b/tests/consumer/README.md index a53b79b1..df15c319 100644 --- a/tests/consumer/README.md +++ b/tests/consumer/README.md @@ -62,8 +62,10 @@ core and Android entrypoints below, plus focused behavior checks: | entry point | symbols | | --- | --- | -| `@rules_flutter_core//flutter:defs.bzl` | `flutter_core`, `flutter_package`, `flutter_pubspec`, `flutter_test`, `pub_path_deps_check`, `pub_plugins_check` | -| `@rules_flutter_android//flutter:defs.bzl` | `flutter_android_app`, `flutter_android_inputs`, `flutter_apk_check`, `flutter_assets`, `flutter_assets_dir`, `flutter_android_manifest`, `flutter_native_contribution`, `flutter_plugin_deps_guards` | +| `@rules_flutter_core//flutter:defs.bzl` | `flutter_core`, `flutter_package`, `flutter_pubspec`, `flutter_test` | +| `@rules_flutter_core//flutter:composition.bzl` | `pub_path_deps_check` | +| `@rules_flutter_android//flutter:defs.bzl` | `flutter_android_app`, `flutter_assets`, `flutter_native_contribution`, `flutter_plugin_deps_guards` | +| `@rules_flutter_android//flutter:composition.bzl` | `flutter_android_inputs`, `flutter_embedding_deps`, `android_native_files`, `FlutterNativeFilesByAbiInfo`, `flutter_plugin_registrant`, `flutter_build_config` | | `@rules_flutter_android//flutter:extensions.bzl` | `flutter_plugins_ext` (`plugins.project()`/`plugins.package()`/`plugins.build_config_field()`) over real, checked-in external plugin graphs — `:fake_plugin_deps_check`, `:fake_plugin_test`, `:namespace_plugin_test`, `:build_config_plugin_jar_test`, `:build_config_plugin_values_test` | Remaining implementation rules and helpers are covered indirectly by the diff --git a/tests/consumer/composed_app/BUILD.bazel b/tests/consumer/composed_app/BUILD.bazel index e9a9bd3d..ebcf07a2 100644 --- a/tests/consumer/composed_app/BUILD.bazel +++ b/tests/consumer/composed_app/BUILD.bazel @@ -10,20 +10,16 @@ load("@rules_android//rules:rules.bzl", "aar_import", "android_binary") load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_import.bzl", "cc_import") load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_flutter_android//flutter:composition.bzl", "android_native_files", "flutter_embedding_deps") load( "@rules_flutter_android//flutter:defs.bzl", "FLUTTER_DEBUG_UNCOMPRESSED_ASSETS", "flutter_android_app", ) - -# Exercise the generated native strip path without adding a public macro. -# buildifier: disable=bzl-visibility -load("@rules_flutter_android//flutter/private:native_strip.bzl", "android_native_files") load("@rules_flutter_core//flutter:defs.bzl", "flutter_core") load("@rules_java//java:defs.bzl", "java_import", "java_library") load("@rules_java//java:java_single_jar.bzl", "java_single_jar") load("@rules_kotlin//kotlin:android.bzl", "kt_android_library") -load("//:embedding_deps.bzl", "EMBEDDING_DEPS") load(":direct_native.bzl", "direct_native") load(":native_files_test.bzl", "native_files_check") @@ -311,19 +307,12 @@ filegroup( tags = ["manual"], ) -filegroup( - name = "direct_embedding_jar", - srcs = [":core"], - output_group = "flutter_embedding", - tags = ["manual"], -) - java_import( name = "direct_embedding", - jars = [":direct_embedding_jar"], + jars = ["@rules_flutter_android//flutter:embedding_jar"], tags = ["manual"], - exports = EMBEDDING_DEPS, - deps = EMBEDDING_DEPS, + exports = flutter_embedding_deps(), + deps = flutter_embedding_deps(), ) direct_native( diff --git a/tests/consumer/composed_app/native_files_test.bzl b/tests/consumer/composed_app/native_files_test.bzl index c3c59e08..ff7bf9b2 100644 --- a/tests/consumer/composed_app/native_files_test.bzl +++ b/tests/consumer/composed_app/native_files_test.bzl @@ -1,8 +1,7 @@ """Analysis checks for the generated native carrier.""" # The generated hub exposes this provider to packaging rules. -# buildifier: disable=bzl-visibility -load("@rules_flutter_android//flutter/private:native_files.bzl", "FlutterNativeFilesByAbiInfo") +load("@rules_flutter_android//flutter:composition.bzl", "FlutterNativeFilesByAbiInfo") def _native_files_check_impl(ctx): files = ctx.attr.carrier[FlutterNativeFilesByAbiInfo].files diff --git a/tests/consumer/embedding_deps.bzl b/tests/consumer/embedding_deps.bzl deleted file mode 100644 index 14938bfa..00000000 --- a/tests/consumer/embedding_deps.bzl +++ /dev/null @@ -1,17 +0,0 @@ -"""Consumer-owned compile dependencies for the Flutter embedding.""" - -EMBEDDING_DEPS = [ - "@flutter_maven//:androidx_lifecycle_lifecycle_common", - "@flutter_maven//:androidx_lifecycle_lifecycle_common_java8", - "@flutter_maven//:androidx_lifecycle_lifecycle_process", - "@flutter_maven//:androidx_lifecycle_lifecycle_runtime", - "@flutter_maven//:androidx_fragment_fragment", - "@flutter_maven//:androidx_annotation_annotation", - "@flutter_maven//:androidx_tracing_tracing", - "@flutter_maven//:androidx_core_core", - "@flutter_maven//:androidx_window_window_java", - "@flutter_maven//:androidx_window_window", - "@flutter_maven//:androidx_exifinterface_exifinterface", - "@flutter_maven//:com_getkeepsafe_relinker_relinker", - "@flutter_maven//:androidx_annotation_annotation_jvm", -] diff --git a/tests/consumer/external_app/app/BUILD.bazel b/tests/consumer/external_app/app/BUILD.bazel index 4c1679c5..8cc740c8 100644 --- a/tests/consumer/external_app/app/BUILD.bazel +++ b/tests/consumer/external_app/app/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_flutter_android//flutter:defs.bzl", "flutter_android_inputs") +load("@rules_flutter_android//flutter:composition.bzl", "flutter_android_inputs") load("@rules_flutter_core//flutter:defs.bzl", "flutter_core") package(default_visibility = ["//visibility:public"]) diff --git a/tests/consumer/fixtures/hand_wired_dev/BUILD.bazel b/tests/consumer/fixtures/hand_wired_dev/BUILD.bazel new file mode 100644 index 00000000..7c2c9a9f --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_dev/BUILD.bazel @@ -0,0 +1,11 @@ +package(default_visibility = ["//hand_wired_app:__pkg__"]) + +filegroup( + name = "sources", + srcs = glob(["**"]), +) + +filegroup( + name = "java_sources", + srcs = glob(["android/**/*.java"]), +) diff --git a/tests/consumer/fixtures/hand_wired_dev/android/src/main/java/com/example/handwired/dev/DevPlugin.java b/tests/consumer/fixtures/hand_wired_dev/android/src/main/java/com/example/handwired/dev/DevPlugin.java new file mode 100644 index 00000000..43149bed --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_dev/android/src/main/java/com/example/handwired/dev/DevPlugin.java @@ -0,0 +1,18 @@ +package com.example.handwired.dev; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import org.apache.commons.lang3.StringUtils; + +public final class DevPlugin implements FlutterPlugin { + private String normalized; + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + normalized = StringUtils.upperCase("dev"); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + normalized = null; + } +} diff --git a/tests/consumer/fixtures/hand_wired_dev/pubspec.yaml b/tests/consumer/fixtures/hand_wired_dev/pubspec.yaml new file mode 100644 index 00000000..30db7106 --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_dev/pubspec.yaml @@ -0,0 +1,2 @@ +name: hand_wired_dev +version: 0.0.1 diff --git a/tests/consumer/fixtures/hand_wired_regular/BUILD.bazel b/tests/consumer/fixtures/hand_wired_regular/BUILD.bazel new file mode 100644 index 00000000..7c2c9a9f --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_regular/BUILD.bazel @@ -0,0 +1,11 @@ +package(default_visibility = ["//hand_wired_app:__pkg__"]) + +filegroup( + name = "sources", + srcs = glob(["**"]), +) + +filegroup( + name = "java_sources", + srcs = glob(["android/**/*.java"]), +) diff --git a/tests/consumer/fixtures/hand_wired_regular/android/src/main/java/com/example/handwired/regular/RegularPlugin.java b/tests/consumer/fixtures/hand_wired_regular/android/src/main/java/com/example/handwired/regular/RegularPlugin.java new file mode 100644 index 00000000..20baf630 --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_regular/android/src/main/java/com/example/handwired/regular/RegularPlugin.java @@ -0,0 +1,19 @@ +package com.example.handwired.regular; + +import com.google.gson.Gson; +import io.flutter.embedding.engine.plugins.FlutterPlugin; + +public final class RegularPlugin implements FlutterPlugin { + private Gson gson; + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + gson = new Gson(); + gson.toJson("regular"); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + gson = null; + } +} diff --git a/tests/consumer/fixtures/hand_wired_regular/pubspec.yaml b/tests/consumer/fixtures/hand_wired_regular/pubspec.yaml new file mode 100644 index 00000000..bc6d4b34 --- /dev/null +++ b/tests/consumer/fixtures/hand_wired_regular/pubspec.yaml @@ -0,0 +1,2 @@ +name: hand_wired_regular +version: 0.0.1 diff --git a/tests/consumer/hand_wired_app/BUILD.bazel b/tests/consumer/hand_wired_app/BUILD.bazel new file mode 100644 index 00000000..a193c48b --- /dev/null +++ b/tests/consumer/hand_wired_app/BUILD.bazel @@ -0,0 +1,115 @@ +load("@rules_android//rules:rules.bzl", "android_binary", "android_library") +load("@rules_flutter_android//flutter:composition.bzl", "flutter_build_config", "flutter_embedding_deps", "flutter_plugin_registrant") +load("@rules_flutter_core//flutter:defs.bzl", "flutter_core") +load("@rules_java//java:defs.bzl", "java_import") + +package(default_visibility = ["//visibility:private"]) + +flutter_core( + name = "core", + srcs = glob(["lib/**/*.dart"]), + path_deps = [ + "//fixtures/hand_wired_regular:sources", + "//fixtures/hand_wired_dev:sources", + ], + pub = "@hand_wired_pub", +) + +filegroup( + name = "flutter_assets", + srcs = [":core"], + output_group = "flutter_assets", +) + +java_import( + name = "flutter_embedding", + jars = ["@rules_flutter_android//flutter:embedding_jar"], + deps = select({ + "@rules_flutter_core//flutter:mode_debug": flutter_embedding_deps("@hand_wired_debug"), + "@rules_flutter_core//flutter:mode_release": flutter_embedding_deps("@hand_wired_release"), + }), +) + +flutter_build_config( + name = "regular_build_config", + package_name = "com.example.handwired.regular", +) + +android_library( + name = "regular_plugin", + srcs = [ + ":regular_build_config", + "//fixtures/hand_wired_regular:java_sources", + ], + deps = [":flutter_embedding"] + select({ + "@rules_flutter_core//flutter:mode_debug": ["@hand_wired_debug//:com_google_code_gson_gson"], + "@rules_flutter_core//flutter:mode_release": ["@hand_wired_release//:com_google_code_gson_gson"], + }), +) + +flutter_build_config( + name = "dev_build_config", + package_name = "com.example.handwired.dev", +) + +android_library( + name = "dev_plugin", + srcs = [ + ":dev_build_config", + "//fixtures/hand_wired_dev:java_sources", + ], + target_compatible_with = select({ + "@rules_flutter_core//flutter:mode_debug": [], + "@rules_flutter_core//flutter:mode_release": ["@platforms//:incompatible"], + }), + deps = [":flutter_embedding"] + select({ + "@rules_flutter_core//flutter:mode_debug": ["@hand_wired_debug//:org_apache_commons_commons_lang3"], + "@rules_flutter_core//flutter:mode_release": ["@hand_wired_release//:org_apache_commons_commons_lang3"], + }), +) + +flutter_plugin_registrant( + name = "registrant_sources", + dev_plugins = ["hand_wired_dev|com.example.handwired.dev.DevPlugin"], + plugins = ["hand_wired_regular|com.example.handwired.regular.RegularPlugin"], +) + +android_library( + name = "registrant", + srcs = [":registrant_sources"], + deps = [":flutter_embedding"] + select({ + "@rules_flutter_core//flutter:mode_debug": [ + ":dev_plugin", + ":regular_plugin", + "@hand_wired_debug//:androidx_annotation_annotation_jvm", + ], + "@rules_flutter_core//flutter:mode_release": [ + ":regular_plugin", + "@hand_wired_release//:androidx_annotation_annotation_jvm", + ], + }), +) + +android_library( + name = "main_activity", + srcs = ["src/main/java/com/example/handwired/MainActivity.java"], + deps = [":flutter_embedding"], +) + +android_binary( + name = "hand_wired_app", + assets = [":flutter_assets"], + assets_dir = "core_bundle_action", + manifest = "src/main/AndroidManifest.xml", + manifest_values = { + "applicationId": "com.example.handwired", + "minSdkVersion": "24", + "targetSdkVersion": "36", + }, + resource_files = [], + deps = [ + ":flutter_embedding", + ":main_activity", + ":registrant", + ], +) diff --git a/tests/consumer/hand_wired_app/lib/main.dart b/tests/consumer/hand_wired_app/lib/main.dart new file mode 100644 index 00000000..3205e85a --- /dev/null +++ b/tests/consumer/hand_wired_app/lib/main.dart @@ -0,0 +1,3 @@ +import 'package:flutter/material.dart'; + +void main() => runApp(const SizedBox.shrink()); diff --git a/tests/consumer/hand_wired_app/pubspec.lock b/tests/consumer/hand_wired_app/pubspec.lock new file mode 100644 index 00000000..612eef92 --- /dev/null +++ b/tests/consumer/hand_wired_app/pubspec.lock @@ -0,0 +1,69 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + hand_wired_dev: + dependency: "direct dev" + description: + path: "../fixtures/hand_wired_dev" + relative: true + source: path + version: "0.0.1" + hand_wired_regular: + dependency: "direct main" + description: + path: "../fixtures/hand_wired_regular" + relative: true + source: path + version: "0.0.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" + url: "https://pub.dev" + source: hosted + version: "1.19.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: fa49e1674cb84acb900141714e6c6e1f9ae099827c24d141430ff6819db4e1bc + url: "https://pub.dev" + source: hosted + version: "2.4.4" +sdks: + dart: ">=3.11.0-0 <4.0.0" diff --git a/tests/consumer/hand_wired_app/pubspec.yaml b/tests/consumer/hand_wired_app/pubspec.yaml new file mode 100644 index 00000000..9e47d01f --- /dev/null +++ b/tests/consumer/hand_wired_app/pubspec.yaml @@ -0,0 +1,18 @@ +name: hand_wired_app +description: Hand-wired Android composition consumer fixture. +publish_to: none +version: 0.0.1 + +environment: + sdk: '>=3.11.0 <4.0.0' + flutter: '>=3.0.0' + +dependencies: + flutter: + sdk: flutter + hand_wired_regular: + path: ../fixtures/hand_wired_regular + +dev_dependencies: + hand_wired_dev: + path: ../fixtures/hand_wired_dev diff --git a/tests/consumer/hand_wired_app/src/main/AndroidManifest.xml b/tests/consumer/hand_wired_app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..88e9c1b9 --- /dev/null +++ b/tests/consumer/hand_wired_app/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/consumer/hand_wired_app/src/main/java/com/example/handwired/MainActivity.java b/tests/consumer/hand_wired_app/src/main/java/com/example/handwired/MainActivity.java new file mode 100644 index 00000000..f364ad14 --- /dev/null +++ b/tests/consumer/hand_wired_app/src/main/java/com/example/handwired/MainActivity.java @@ -0,0 +1,5 @@ +package com.example.handwired; + +import io.flutter.embedding.android.FlutterActivity; + +public final class MainActivity extends FlutterActivity {} diff --git a/tests/namespace_dynamic/MODULE.bazel.lock b/tests/namespace_dynamic/MODULE.bazel.lock index 25fca3bd..87796720 100644 --- a/tests/namespace_dynamic/MODULE.bazel.lock +++ b/tests/namespace_dynamic/MODULE.bazel.lock @@ -404,7 +404,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%android_ndk": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "EZM/Ob3Auw0v6B0CQI9nhTMvFryd8ZLaCRIMfYFeY0s=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android rules_android+", @@ -426,7 +426,7 @@ }, "@@sameri11_rules_flutter_android+//flutter:extensions.bzl%flutter_plugins_ext": { "general": { - "bzlTransitiveDigest": "yzp9FeetIGD56cfZeH1j8ojw8Cb1c6sMqvJb1OozAQs=", + "bzlTransitiveDigest": "ChmCXXuZkbfT0XtrGZ3VW6ftH+vEyMOroOzRtbHokuY=", "usagesDigest": "RqVa86t+EWrrGg+0ZGJnQnjPe2QKakZHbi5XlHpeEh8=", "recordedInputs": [ "REPO_MAPPING:,dynamic_namespace_metadata +dynamic_namespace_metadata+dynamic_namespace_metadata", @@ -434,8 +434,6 @@ "REPO_MAPPING:sameri11_rules_flutter_android+,rules_android_ndk rules_android_ndk+", "REPO_MAPPING:sameri11_rules_flutter_android+,rules_flutter_core sameri11_rules_flutter_core+", "REPO_MAPPING:sameri11_rules_flutter_core+,platforms platforms", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_debug sameri11_rules_flutter_core++flutter+flutter_embedding_debug", - "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_embedding_release sameri11_rules_flutter_core++flutter+flutter_embedding_release", "REPO_MAPPING:sameri11_rules_flutter_android+,androidndk_cmake sameri11_rules_flutter_android++android_ndk+androidndk_cmake" ], "generatedRepoSpecs": { @@ -445,8 +443,8 @@ "abis": [ "arm64-v8a" ], - "embedding_debug": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_debug//jar:file", - "embedding_release": "@@sameri11_rules_flutter_core++flutter+flutter_embedding_release//jar:file", + "embedding_debug": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_debug", + "embedding_release": "@@sameri11_rules_flutter_android+//flutter:embedding_jar_release", "metadata": "@@+dynamic_namespace_metadata+dynamic_namespace_metadata//:plugins_metadata.json", "package_config": "@@+dynamic_namespace_metadata+dynamic_namespace_metadata//:package_config.json", "coursier_options": [], @@ -458,7 +456,6 @@ "consumer_rules": {}, "build_config_fields": {}, "recipes": {}, - "native_strip_bzl": "'@@sameri11_rules_flutter_android+//flutter/private:native_strip.bzl'", "mode_debug": "'@@sameri11_rules_flutter_core+//flutter:mode_debug'", "mode_release": "'@@sameri11_rules_flutter_core+//flutter:mode_release'", "ndk_source_properties": "'@@sameri11_rules_flutter_android++android_ndk+androidndk_cmake//:ndk_source_properties'" @@ -467,9 +464,32 @@ } } }, + "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_android_embedding": { + "general": { + "bzlTransitiveDigest": "Cc2IsXwVitTCSUMnh/0e+uh1l6r143p3g2wxU9QzBGg=", + "usagesDigest": "VDubd1CC/3EsTr4xz8tz3uu65g6E5r8qQxmJ5I7keKE=", + "recordedInputs": [ + "REPO_MAPPING:sameri11_rules_flutter_android+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk" + ], + "generatedRepoSpecs": { + "flutter_embedding_debug": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "debug" + } + }, + "flutter_embedding_release": { + "repoRuleId": "@@sameri11_rules_flutter_android+//flutter/private:embedding_repo.bzl%flutter_embedding_jar", + "attributes": { + "mode": "release" + } + } + } + } + }, "@@sameri11_rules_flutter_core+//flutter:extensions.bzl%flutter": { "general": { - "bzlTransitiveDigest": "IQKKhDRQ2nv9172jyBf+8QrvfO6OoJlhXoR8YS+5fA8=", + "bzlTransitiveDigest": "AARGgu9wwVVVg2ko+pKNhSOGTtOh12+1knTwjHuxpjo=", "usagesDigest": "4778tf6hjNJFXpm3IbdSjOP2Pn3Qa43Y+4cT+Z0KOnk=", "recordedInputs": [ "REPO_MAPPING:sameri11_rules_flutter_core+,flutter_sdk sameri11_rules_flutter_core++flutter+flutter_sdk", @@ -486,18 +506,6 @@ "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_tools_packages", "attributes": {} }, - "flutter_embedding_release": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_release" - } - }, - "flutter_embedding_debug": { - "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", - "attributes": { - "artifact": "flutter_embedding_debug" - } - }, "flutter_engine_arm64_v8a_release": { "repoRuleId": "@@sameri11_rules_flutter_core+//flutter/private:repo.bzl%flutter_engine_jar", "attributes": { diff --git a/tools/ci/hand_wired_dex_check.py b/tools/ci/hand_wired_dex_check.py new file mode 100755 index 00000000..6743d3ee --- /dev/null +++ b/tools/ci/hand_wired_dex_check.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Assert the hand-wired app's regular/dev plugins and Maven classes in DEX.""" + +import argparse +import sys +import zipfile +from pathlib import Path + +REGULAR = ( + b"Lcom/example/handwired/regular/RegularPlugin;", + b"Lcom/google/gson/Gson;", +) +DEV = ( + b"Lcom/example/handwired/dev/DevPlugin;", + b"Lorg/apache/commons/lang3/StringUtils;", +) + + +def check(mode, apk): + apk = Path(apk) + if not apk.is_file(): + raise ValueError("APK does not exist: {}".format(apk)) + with zipfile.ZipFile(apk) as archive: + dex_files = [name for name in archive.namelist() if name.startswith("classes") and name.endswith(".dex")] + if not dex_files: + raise ValueError("APK contains no DEX files: {}".format(apk)) + contents = b"".join(archive.read(name) for name in dex_files) + expected = REGULAR + (DEV if mode == "debug" else ()) + absent = [descriptor.decode() for descriptor in expected if descriptor not in contents] + forbidden = [descriptor.decode() for descriptor in (DEV if mode == "release" else ()) if descriptor in contents] + if absent or forbidden: + raise ValueError("{} DEX class mismatch: missing={}, unexpected={}".format(mode, absent, forbidden)) + print("PASS: {} DEX has regular plugin/Gson{}".format(mode, " and dev plugin/Commons Lang" if mode == "debug" else "; dev plugin/Commons Lang absent")) + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("mode", choices=("debug", "release")) + parser.add_argument("apk", type=Path) + args = parser.parse_args(argv) + check(args.mode, args.apk) + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (OSError, ValueError, zipfile.BadZipFile) as error: + print("FAIL: {}".format(error), file=sys.stderr) + sys.exit(1) diff --git a/tools/ci/plugin_hub_boundary_guard.py b/tools/ci/plugin_hub_boundary_guard.py new file mode 100755 index 00000000..a1d001f5 --- /dev/null +++ b/tools/ci/plugin_hub_boundary_guard.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Reject generated plugin hubs that load ruleset-private modules.""" + +import argparse +import re +import subprocess +import sys +import tempfile +from pathlib import Path +from unittest.mock import patch + + +def check(build_file): + root_build = Path(build_file) + if not root_build.is_file(): + raise ValueError("missing generated hub BUILD file: {}".format(root_build)) + build_files = sorted( + set(root_build.parent.rglob("BUILD")) | set(root_build.parent.rglob("BUILD.bazel")) + ) + if not build_files: + raise ValueError("generated hub has no BUILD files under: {}".format(root_build.parent)) + for path in build_files: + text = path.read_text() + if not text.strip(): + raise ValueError("empty generated hub BUILD file: {}".format(path)) + for match in re.finditer(r"load\s*\(\s*[\"']([^\"']+)[\"']", text, re.DOTALL): + label = match.group(1) + if "/private" in label: + raise ValueError("{} loads a private module: {}".format(path, label)) + + +def _hub_build(label): + result = subprocess.run( + ["bazel", "query", "--output=location", label], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + if result.returncode: + raise ValueError("hub query failed for {}:\n{}".format(label, result.stdout)) + locations = [] + for line in result.stdout.splitlines(): + match = re.match(r"^(.+?BUILD(?:\.bazel)?):\d+:\d+:", line) + if match: + locations.append(match.group(1)) + if len(locations) != 1: + raise ValueError("hub query for {} returned no unique BUILD location:\n{}".format(label, result.stdout)) + return locations[0] + + +def selftest(): + label = "@flutter_plugins//:generated_plugin_registrant_src" + with tempfile.TemporaryDirectory(prefix="flutter-hub-boundary-") as temp: + build = Path(temp) / "BUILD.bazel" + location = "{}:7:1: flutter_plugin_registrant rule {}".format(build, label) + + def run_main(content, expected, nested=False, query_output=None, returncode=0): + if content is None: + build.unlink(missing_ok=True) + else: + build.write_text(content) + nested_build = build.parent / "pkg" / "BUILD.bazel" + if nested: + nested_build.parent.mkdir() + nested_build.write_text('load("@rules_flutter_android//flutter/private:x.bzl", "x")\n') + else: + nested_build.unlink(missing_ok=True) + result = subprocess.CompletedProcess( + ["bazel", "query"], + returncode, + stdout=(location + "\n") if query_output is None else query_output, + ) + with patch("subprocess.run", return_value=result): + try: + main([label]) + except ValueError as error: + if expected not in str(error): + raise + else: + raise ValueError("hub guard accepted input expected to fail: {}".format(expected)) + + run_main( + 'load(\n "@rules_flutter_android//flutter/private:x.bzl",\n "x",\n)\n', + "private module", + ) + run_main("filegroup(name = 'target')\n", "private module", nested=True) + run_main("\n", "empty") + run_main(None, "missing") + run_main("filegroup(name = 'target')\n", "query failed", returncode=1, query_output="query error") + run_main("filegroup(name = 'target')\n", "no unique BUILD location", query_output="") + print("PASS: hub guard rejects private loads in root and nested BUILD files, missing, empty, query-error and empty-result inputs") + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("labels", nargs="*", help="Bazel labels in generated plugin repositories") + parser.add_argument("--selftest", action="store_true") + args = parser.parse_args(argv) + if args.selftest: + selftest() + for label in args.labels: + path = _hub_build(label) + check(path) + print("PASS: {} loads no private module ({})".format(label, path)) + if not args.selftest and not args.labels: + parser.error("provide hub labels or --selftest") + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except ValueError as error: + print("FAIL: {}".format(error), file=sys.stderr) + sys.exit(1)