diff --git a/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/.gitignore b/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/.gitignore index e2713a2779c..b2aa40502ce 100644 --- a/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/.gitignore +++ b/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/.gitignore @@ -3,4 +3,7 @@ /.preview /build /.cxx -/.test \ No newline at end of file +/.test +**/BuildProfile.ets +**/oh-package-lock.json5 +**/libs/** diff --git a/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/src/main/cpp/CMakeLists.txt b/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/src/main/cpp/CMakeLists.txt index 0a80a95c903..df7d9d72c8c 100644 --- a/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/src/main/cpp/CMakeLists.txt +++ b/frb_codegen/assets/integration_template/cargokit/app/rust_builder/ohos/src/main/cpp/CMakeLists.txt @@ -8,15 +8,17 @@ project(${PROJECT_NAME} LANGUAGES CXX) if (NOT DEFINED FLUTTER_TARGET_PLATFORM) if (OHOS_ARCH STREQUAL "arm64-v8a") set(FLUTTER_TARGET_PLATFORM "ohos-arm64") - elseif(OHOS_ARCH STREQUAL "armeabi-v7a") - set(FLUTTER_TARGET_PLATFORM "ohos-arm") elseif(OHOS_ARCH STREQUAL "x86_64") set(FLUTTER_TARGET_PLATFORM "ohos-x64") + elseif(OHOS_ARCH STREQUAL "armeabi-v7a") + message(FATAL_ERROR + "The current HarmonyOS Flutter/Hvigor toolchain does not support " + "the armeabi-v7a ABI. Use arm64-v8a or x86_64.") else() - message(WARNING "Unknown OHOS_ARCH: ${OHOS_ARCH}, defaulting to ohos-arm64") - set(FLUTTER_TARGET_PLATFORM "ohos-arm64") + message(FATAL_ERROR + "Unsupported OHOS_ARCH: ${OHOS_ARCH}. Use arm64-v8a or x86_64.") endif() - message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") + message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") endif() include("../../../../cargokit/cmake/cargokit.cmake") apply_cargokit(${PROJECT_NAME} ../../../../../rust REPLACE_ME_RUST_CRATE_NAME "") diff --git a/frb_codegen/assets/integration_template/cargokit/plugin/ohos/.gitignore b/frb_codegen/assets/integration_template/cargokit/plugin/ohos/.gitignore index e2713a2779c..b2aa40502ce 100644 --- a/frb_codegen/assets/integration_template/cargokit/plugin/ohos/.gitignore +++ b/frb_codegen/assets/integration_template/cargokit/plugin/ohos/.gitignore @@ -3,4 +3,7 @@ /.preview /build /.cxx -/.test \ No newline at end of file +/.test +**/BuildProfile.ets +**/oh-package-lock.json5 +**/libs/** diff --git a/frb_codegen/assets/integration_template/cargokit/plugin/ohos/src/main/cpp/CMakeLists.txt b/frb_codegen/assets/integration_template/cargokit/plugin/ohos/src/main/cpp/CMakeLists.txt index ba0be730361..26b26b3186a 100644 --- a/frb_codegen/assets/integration_template/cargokit/plugin/ohos/src/main/cpp/CMakeLists.txt +++ b/frb_codegen/assets/integration_template/cargokit/plugin/ohos/src/main/cpp/CMakeLists.txt @@ -8,15 +8,17 @@ project(${PROJECT_NAME} LANGUAGES CXX) if (NOT DEFINED FLUTTER_TARGET_PLATFORM) if (OHOS_ARCH STREQUAL "arm64-v8a") set(FLUTTER_TARGET_PLATFORM "ohos-arm64") - elseif(OHOS_ARCH STREQUAL "armeabi-v7a") - set(FLUTTER_TARGET_PLATFORM "ohos-arm") elseif(OHOS_ARCH STREQUAL "x86_64") set(FLUTTER_TARGET_PLATFORM "ohos-x64") + elseif(OHOS_ARCH STREQUAL "armeabi-v7a") + message(FATAL_ERROR + "The current HarmonyOS Flutter/Hvigor toolchain does not support " + "the armeabi-v7a ABI. Use arm64-v8a or x86_64.") else() - message(WARNING "Unknown OHOS_ARCH: ${OHOS_ARCH}, defaulting to ohos-arm64") - set(FLUTTER_TARGET_PLATFORM "ohos-arm64") + message(FATAL_ERROR + "Unsupported OHOS_ARCH: ${OHOS_ARCH}. Use arm64-v8a or x86_64.") endif() - message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") + message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") endif() include("../../../../cargokit/cmake/cargokit.cmake") apply_cargokit(${PROJECT_NAME} ../../../../rust REPLACE_ME_RUST_CRATE_NAME "") diff --git a/frb_example/flutter_via_create/lib/src/rust/api/ohos_smoke.dart b/frb_example/flutter_via_create/lib/src/rust/api/ohos_smoke.dart new file mode 100644 index 00000000000..102958a532e --- /dev/null +++ b/frb_example/flutter_via_create/lib/src/rust/api/ohos_smoke.dart @@ -0,0 +1,41 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.14.0-beta.2. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone` + +Uint8List reverseBytes({required List value}) => + RustLib.instance.api.crateApiOhosSmokeReverseBytes(value: value); + +OhosSmokePayload transformPayload({required OhosSmokePayload value}) => + RustLib.instance.api.crateApiOhosSmokeTransformPayload(value: value); + +Future asyncGreet({required String name}) => + RustLib.instance.api.crateApiOhosSmokeAsyncGreet(name: name); + +String fallibleValue({required bool shouldFail}) => + RustLib.instance.api.crateApiOhosSmokeFallibleValue(shouldFail: shouldFail); + +Stream emitValues() => RustLib.instance.api.crateApiOhosSmokeEmitValues(); + +class OhosSmokePayload { + final int code; + final String label; + + const OhosSmokePayload({required this.code, required this.label}); + + @override + int get hashCode => code.hashCode ^ label.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is OhosSmokePayload && + runtimeType == other.runtimeType && + code == other.code && + label == other.label; +} diff --git a/frb_example/flutter_via_create/lib/src/rust/frb_generated.dart b/frb_example/flutter_via_create/lib/src/rust/frb_generated.dart index 07ddb815cf4..45643990220 100644 --- a/frb_example/flutter_via_create/lib/src/rust/frb_generated.dart +++ b/frb_example/flutter_via_create/lib/src/rust/frb_generated.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/ohos_smoke.dart'; import 'api/simple.dart'; import 'dart:async'; import 'dart:convert'; @@ -66,7 +67,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.14.0-beta.2'; @override - int get rustContentHash => -1918914929; + int get rustContentHash => -706662123; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -78,9 +79,21 @@ class RustLib extends BaseEntrypoint { } abstract class RustLibApi extends BaseApi { + Future crateApiOhosSmokeAsyncGreet({required String name}); + + Stream crateApiOhosSmokeEmitValues(); + + String crateApiOhosSmokeFallibleValue({required bool shouldFail}); + String crateApiSimpleGreet({required String name}); Future crateApiSimpleInitApp(); + + Uint8List crateApiOhosSmokeReverseBytes({required List value}); + + OhosSmokePayload crateApiOhosSmokeTransformPayload({ + required OhosSmokePayload value, + }); } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -91,6 +104,107 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { required super.portManager, }); + @override + Future crateApiOhosSmokeAsyncGreet({required String name}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + try { + sse_encode_String(name, serializer); + } catch (_) { + serializer.dispose(); + rethrow; + } + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 1, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: null, + ), + constMeta: kCrateApiOhosSmokeAsyncGreetConstMeta, + argValues: [name], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiOhosSmokeAsyncGreetConstMeta => + const TaskConstMeta(debugName: "async_greet", argNames: ["name"]); + + @override + Stream crateApiOhosSmokeEmitValues() { + final sink = RustStreamSink(); + unawaited( + handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + try { + sse_encode_StreamSink_u_32_Sse(sink, serializer); + } catch (_) { + serializer.dispose(); + rethrow; + } + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 2, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiOhosSmokeEmitValuesConstMeta, + argValues: [sink], + apiImpl: this, + ), + ), + ); + return sink.stream; + } + + TaskConstMeta get kCrateApiOhosSmokeEmitValuesConstMeta => + const TaskConstMeta(debugName: "emit_values", argNames: ["sink"]); + + @override + String crateApiOhosSmokeFallibleValue({required bool shouldFail}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + try { + sse_encode_bool(shouldFail, serializer); + } catch (_) { + serializer.dispose(); + rethrow; + } + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_String, + ), + constMeta: kCrateApiOhosSmokeFallibleValueConstMeta, + argValues: [shouldFail], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiOhosSmokeFallibleValueConstMeta => + const TaskConstMeta( + debugName: "fallible_value", + argNames: ["shouldFail"], + ); + @override String crateApiSimpleGreet({required String name}) { return handler.executeSync( @@ -103,7 +217,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.dispose(); rethrow; } - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -128,7 +242,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 2, + funcId: 5, port: port_, ); }, @@ -146,18 +260,130 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiSimpleInitAppConstMeta => const TaskConstMeta(debugName: "init_app", argNames: []); + @override + Uint8List crateApiOhosSmokeReverseBytes({required List value}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + try { + sse_encode_list_prim_u_8_loose(value, serializer); + } catch (_) { + serializer.dispose(); + rethrow; + } + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: null, + ), + constMeta: kCrateApiOhosSmokeReverseBytesConstMeta, + argValues: [value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiOhosSmokeReverseBytesConstMeta => + const TaskConstMeta(debugName: "reverse_bytes", argNames: ["value"]); + + @override + OhosSmokePayload crateApiOhosSmokeTransformPayload({ + required OhosSmokePayload value, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + try { + sse_encode_box_autoadd_ohos_smoke_payload(value, serializer); + } catch (_) { + serializer.dispose(); + rethrow; + } + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_ohos_smoke_payload, + decodeErrorData: null, + ), + constMeta: kCrateApiOhosSmokeTransformPayloadConstMeta, + argValues: [value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiOhosSmokeTransformPayloadConstMeta => + const TaskConstMeta(debugName: "transform_payload", argNames: ["value"]); + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnyhowException(dcoDecodeString(raw)); + } + + @protected + RustStreamSink dco_decode_StreamSink_u_32_Sse(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + @protected String dco_decode_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return dcoDecodeString(raw); } + @protected + bool dco_decode_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + OhosSmokePayload dco_decode_box_autoadd_ohos_smoke_payload(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_ohos_smoke_payload(raw); + } + + @protected + int dco_decode_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodePrimitiveInt(raw); + } + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as List; + } + @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as Uint8List; } + @protected + OhosSmokePayload dco_decode_ohos_smoke_payload(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = dcoDecodeList(raw); + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return OhosSmokePayload( + code: dco_decode_i_32(arr[0]), + label: dco_decode_String(arr[1]), + ); + } + + @protected + int dco_decode_u_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodePrimitiveInt(raw); + } + @protected int dco_decode_u_8(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -170,6 +396,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return; } + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_String(deserializer); + return AnyhowException(inner); + } + + @protected + RustStreamSink sse_decode_StreamSink_u_32_Sse( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + throw UnimplementedError('Unreachable ()'); + } + @protected String sse_decode_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -177,6 +418,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + OhosSmokePayload sse_decode_box_autoadd_ohos_smoke_payload( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_ohos_smoke_payload(deserializer)); + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -184,6 +452,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8List(len_); } + @protected + OhosSmokePayload sse_decode_ohos_smoke_payload(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_code = sse_decode_i_32(deserializer); + var var_label = sse_decode_String(deserializer); + return OhosSmokePayload(code: var_code, label: var_label); + } + + @protected + int sse_decode_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint32(); + } + @protected int sse_decode_u_8(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -196,15 +478,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } @protected - int sse_decode_i_32(SseDeserializer deserializer) { + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ) { // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getInt32(); + sse_encode_String(self.message, serializer); } @protected - bool sse_decode_bool(SseDeserializer deserializer) { + void sse_encode_StreamSink_u_32_Sse( + RustStreamSink self, + SseSerializer serializer, + ) { // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getUint8() != 0; + sse_encode_String( + self.setupAndSerialize( + codec: SseCodec( + decodeSuccessData: sse_decode_u_32, + decodeErrorData: sse_decode_AnyhowException, + ), + ), + serializer, + ); } @protected @@ -213,6 +509,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } + + @protected + void sse_encode_box_autoadd_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_ohos_smoke_payload(self, serializer); + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + + @protected + void sse_encode_list_prim_u_8_loose( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List( + self is Uint8List ? self : Uint8List.fromList(self), + ); + } + @protected void sse_encode_list_prim_u_8_strict( Uint8List self, @@ -224,25 +553,29 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } @protected - void sse_encode_u_8(int self, SseSerializer serializer) { + void sse_encode_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ) { // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putUint8(self); + sse_encode_i_32(self.code, serializer); + sse_encode_String(self.label, serializer); } @protected - void sse_encode_unit(void self, SseSerializer serializer) { + void sse_encode_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint32(self); } @protected - void sse_encode_i_32(int self, SseSerializer serializer) { + void sse_encode_u_8(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putInt32(self); + serializer.buffer.putUint8(self); } @protected - void sse_encode_bool(bool self, SseSerializer serializer) { + void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putUint8(self ? 1 : 0); } } diff --git a/frb_example/flutter_via_create/lib/src/rust/frb_generated.io.dart b/frb_example/flutter_via_create/lib/src/rust/frb_generated.io.dart index 41ec0eee9a9..6ebcffa343f 100644 --- a/frb_example/flutter_via_create/lib/src/rust/frb_generated.io.dart +++ b/frb_example/flutter_via_create/lib/src/rust/frb_generated.io.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/ohos_smoke.dart'; import 'api/simple.dart'; import 'dart:async'; import 'dart:convert'; @@ -18,24 +19,76 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_u_32_Sse(dynamic raw); + @protected String dco_decode_String(dynamic raw); + @protected + bool dco_decode_bool(dynamic raw); + + @protected + OhosSmokePayload dco_decode_box_autoadd_ohos_smoke_payload(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw); + @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + OhosSmokePayload dco_decode_ohos_smoke_payload(dynamic raw); + + @protected + int dco_decode_u_32(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @protected void dco_decode_unit(dynamic raw); + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_u_32_Sse( + SseDeserializer deserializer, + ); + @protected String sse_decode_String(SseDeserializer deserializer); + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + OhosSmokePayload sse_decode_box_autoadd_ohos_smoke_payload( + SseDeserializer deserializer, + ); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); + @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + OhosSmokePayload sse_decode_ohos_smoke_payload(SseDeserializer deserializer); + + @protected + int sse_decode_u_32(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -43,14 +96,35 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_decode_unit(SseDeserializer deserializer); @protected - int sse_decode_i_32(SseDeserializer deserializer); + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); @protected - bool sse_decode_bool(SseDeserializer deserializer); + void sse_encode_StreamSink_u_32_Sse( + RustStreamSink self, + SseSerializer serializer, + ); @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); + @protected void sse_encode_list_prim_u_8_strict( Uint8List self, @@ -58,16 +132,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { ); @protected - void sse_encode_u_8(int self, SseSerializer serializer); + void sse_encode_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ); @protected - void sse_encode_unit(void self, SseSerializer serializer); + void sse_encode_u_32(int self, SseSerializer serializer); @protected - void sse_encode_i_32(int self, SseSerializer serializer); + void sse_encode_u_8(int self, SseSerializer serializer); @protected - void sse_encode_bool(bool self, SseSerializer serializer); + void sse_encode_unit(void self, SseSerializer serializer); } // Section: wire_class diff --git a/frb_example/flutter_via_create/lib/src/rust/frb_generated.web.dart b/frb_example/flutter_via_create/lib/src/rust/frb_generated.web.dart index d9ef4f40425..7976f076e7c 100644 --- a/frb_example/flutter_via_create/lib/src/rust/frb_generated.web.dart +++ b/frb_example/flutter_via_create/lib/src/rust/frb_generated.web.dart @@ -6,6 +6,7 @@ // Static analysis wrongly picks the IO variant, thus ignore this // ignore_for_file: argument_type_not_assignable +import 'api/ohos_smoke.dart'; import 'api/simple.dart'; import 'dart:async'; import 'dart:convert'; @@ -20,24 +21,76 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + RustStreamSink dco_decode_StreamSink_u_32_Sse(dynamic raw); + @protected String dco_decode_String(dynamic raw); + @protected + bool dco_decode_bool(dynamic raw); + + @protected + OhosSmokePayload dco_decode_box_autoadd_ohos_smoke_payload(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw); + @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + OhosSmokePayload dco_decode_ohos_smoke_payload(dynamic raw); + + @protected + int dco_decode_u_32(dynamic raw); + @protected int dco_decode_u_8(dynamic raw); @protected void dco_decode_unit(dynamic raw); + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + RustStreamSink sse_decode_StreamSink_u_32_Sse( + SseDeserializer deserializer, + ); + @protected String sse_decode_String(SseDeserializer deserializer); + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + OhosSmokePayload sse_decode_box_autoadd_ohos_smoke_payload( + SseDeserializer deserializer, + ); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); + @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + OhosSmokePayload sse_decode_ohos_smoke_payload(SseDeserializer deserializer); + + @protected + int sse_decode_u_32(SseDeserializer deserializer); + @protected int sse_decode_u_8(SseDeserializer deserializer); @@ -45,14 +98,35 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_decode_unit(SseDeserializer deserializer); @protected - int sse_decode_i_32(SseDeserializer deserializer); + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); @protected - bool sse_decode_bool(SseDeserializer deserializer); + void sse_encode_StreamSink_u_32_Sse( + RustStreamSink self, + SseSerializer serializer, + ); @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); + @protected void sse_encode_list_prim_u_8_strict( Uint8List self, @@ -60,16 +134,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { ); @protected - void sse_encode_u_8(int self, SseSerializer serializer); + void sse_encode_ohos_smoke_payload( + OhosSmokePayload self, + SseSerializer serializer, + ); @protected - void sse_encode_unit(void self, SseSerializer serializer); + void sse_encode_u_32(int self, SseSerializer serializer); @protected - void sse_encode_i_32(int self, SseSerializer serializer); + void sse_encode_u_8(int self, SseSerializer serializer); @protected - void sse_encode_bool(bool self, SseSerializer serializer); + void sse_encode_unit(void self, SseSerializer serializer); } // Section: wire_class diff --git a/frb_example/flutter_via_create/ohos/entry/src/main/module.json5 b/frb_example/flutter_via_create/ohos/entry/src/main/module.json5 index 5bd7950ba53..0ca0f1d0620 100644 --- a/frb_example/flutter_via_create/ohos/entry/src/main/module.json5 +++ b/frb_example/flutter_via_create/ohos/entry/src/main/module.json5 @@ -6,7 +6,8 @@ "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": [ - "phone" + "default", + "tablet" ], "deliveryWithInstall": true, "installationFree": false, @@ -37,4 +38,4 @@ {"name" : "ohos.permission.INTERNET"}, ] } -} \ No newline at end of file +} diff --git a/frb_example/flutter_via_create/ohos/ohos_device_smoke_main.dart b/frb_example/flutter_via_create/ohos/ohos_device_smoke_main.dart index ebda156b56b..fd88fe067b3 100644 --- a/frb_example/flutter_via_create/ohos/ohos_device_smoke_main.dart +++ b/frb_example/flutter_via_create/ohos/ohos_device_smoke_main.dart @@ -1,14 +1,49 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; +import 'package:flutter_via_create/src/rust/api/ohos_smoke.dart'; import 'package:flutter_via_create/src/rust/api/simple.dart'; import 'package:flutter_via_create/src/rust/frb_generated.dart'; Future main() async { await RustLib.init(); - final result = greet(name: 'Tom'); - debugPrint( - 'FRB_OHOS_SMOKE_RESULT=${result == 'Hello, Tom!' ? 'PASS' : 'FAIL'}', + final greeting = greet(name: 'Tom'); + final bytes = reverseBytes(value: Uint8List.fromList([1, 2, 3, 5])); + final payload = transformPayload( + value: OhosSmokePayload(code: 41, label: 'payload'), ); - runApp(_SmokeApp(result: result)); + final asyncGreeting = await asyncGreet(name: 'Tom'); + final successfulValue = fallibleValue(shouldFail: false); + var failureWasReported = false; + try { + fallibleValue(shouldFail: true); + } on Object { + failureWasReported = true; + } + final streamedValues = []; + await for (final value in emitValues()) { + streamedValues.add(value); + } + + final passed = + greeting == 'Hello, Tom!' && + listEquals(bytes, const [5, 3, 2, 1]) && + payload.code == 42 && + payload.label == 'payload!' && + asyncGreeting == 'Async hello, Tom!' && + successfulValue == 'smoke ok' && + failureWasReported && + listEquals(streamedValues, const [7, 11, 13]); + debugPrint('FRB_OHOS_SMOKE_RESULT=${passed ? 'PASS' : 'FAIL'}'); + runApp(_SmokeApp(result: passed ? 'PASS' : 'FAIL')); +} + +bool listEquals(List first, List second) { + if (first.length != second.length) return false; + for (var index = 0; index < first.length; index++) { + if (first[index] != second[index]) return false; + } + return true; } class _SmokeApp extends StatelessWidget { diff --git a/frb_example/flutter_via_create/rust/src/api/mod.rs b/frb_example/flutter_via_create/rust/src/api/mod.rs index b252f36bf9e..7c9bc831e5f 100644 --- a/frb_example/flutter_via_create/rust/src/api/mod.rs +++ b/frb_example/flutter_via_create/rust/src/api/mod.rs @@ -1 +1,2 @@ +pub mod ohos_smoke; pub mod simple; diff --git a/frb_example/flutter_via_create/rust/src/api/ohos_smoke.rs b/frb_example/flutter_via_create/rust/src/api/ohos_smoke.rs new file mode 100644 index 00000000000..e22ce623e72 --- /dev/null +++ b/frb_example/flutter_via_create/rust/src/api/ohos_smoke.rs @@ -0,0 +1,39 @@ +use crate::frb_generated::StreamSink; + +#[derive(Clone)] +pub struct OhosSmokePayload { + pub code: i32, + pub label: String, +} + +#[flutter_rust_bridge::frb(sync)] +pub fn reverse_bytes(value: Vec) -> Vec { + value.into_iter().rev().collect() +} + +#[flutter_rust_bridge::frb(sync)] +pub fn transform_payload(value: OhosSmokePayload) -> OhosSmokePayload { + OhosSmokePayload { + code: value.code + 1, + label: format!("{}!", value.label), + } +} + +pub async fn async_greet(name: String) -> String { + format!("Async hello, {name}!") +} + +#[flutter_rust_bridge::frb(sync)] +pub fn fallible_value(should_fail: bool) -> Result { + if should_fail { + Err("expected smoke failure".to_owned()) + } else { + Ok("smoke ok".to_owned()) + } +} + +pub fn emit_values(sink: StreamSink) { + let _ = sink.add(7); + let _ = sink.add(11); + let _ = sink.add(13); +} diff --git a/frb_example/flutter_via_create/rust/src/frb_generated.rs b/frb_example/flutter_via_create/rust/src/frb_generated.rs index 05dc6967452..f5b04aa28f3 100644 --- a/frb_example/flutter_via_create/rust/src/frb_generated.rs +++ b/frb_example/flutter_via_create/rust/src/frb_generated.rs @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.14.0-beta.2"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1918914929; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -706662123; // Section: executor @@ -47,6 +47,111 @@ flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs +fn wire__crate__api__ohos_smoke__async_greet_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "async_greet", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_name = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let output_ok = + Ok::<_, ()>(crate::api::ohos_smoke::async_greet(api_name).await)?; + std::result::Result::Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__ohos_smoke__emit_values_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "emit_values", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_sink = + >::sse_decode( + &mut deserializer, + ); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Ok::<_, ()>({ + crate::api::ohos_smoke::emit_values(api_sink); + })?; + std::result::Result::Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__ohos_smoke__fallible_value_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "fallible_value", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_should_fail = ::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::ohos_smoke::fallible_value(api_should_fail)?; + std::result::Result::Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__simple__greet_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -111,9 +216,86 @@ fn wire__crate__api__simple__init_app_impl( }, ) } +fn wire__crate__api__ohos_smoke__reverse_bytes_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "reverse_bytes", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_value = >::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Ok::<_, ()>(crate::api::ohos_smoke::reverse_bytes(api_value))?; + std::result::Result::Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__ohos_smoke__transform_payload_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "transform_payload", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_value = + ::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Ok::<_, ()>(crate::api::ohos_smoke::transform_payload(api_value))?; + std::result::Result::Ok(output_ok) + })()) + }, + ) +} // Section: dart2rust +impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::anyhow::anyhow!("{}", inner); + } +} + +impl SseDecode for StreamSink { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return StreamSink::deserialize(inner); + } +} + impl SseDecode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -122,6 +304,20 @@ impl SseDecode for String { } } +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -134,30 +330,35 @@ impl SseDecode for Vec { } } -impl SseDecode for u8 { +impl SseDecode for crate::api::ohos_smoke::OhosSmokePayload { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_u8().unwrap() + let mut var_code = ::sse_decode(deserializer); + let mut var_label = ::sse_decode(deserializer); + return crate::api::ohos_smoke::OhosSmokePayload { + code: var_code, + label: var_label, + }; } } -impl SseDecode for () { +impl SseDecode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u32::().unwrap() + } } -impl SseDecode for i32 { +impl SseDecode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_i32::().unwrap() + deserializer.cursor.read_u8().unwrap() } } -impl SseDecode for bool { +impl SseDecode for () { // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_u8().unwrap() != 0 - } + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} } fn pde_ffi_dispatcher_primary_impl( @@ -169,7 +370,9 @@ fn pde_ffi_dispatcher_primary_impl( ) { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 2 => wire__crate__api__simple__init_app_impl(port, ptr, rust_vec_len, data_len), + 1 => wire__crate__api__ohos_smoke__async_greet_impl(port, ptr, rust_vec_len, data_len), + 2 => wire__crate__api__ohos_smoke__emit_values_impl(port, ptr, rust_vec_len, data_len), + 5 => wire__crate__api__simple__init_app_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -182,13 +385,52 @@ fn pde_ffi_dispatcher_sync_impl( ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 1 => wire__crate__api__simple__greet_impl(ptr, rust_vec_len, data_len), + 3 => wire__crate__api__ohos_smoke__fallible_value_impl(ptr, rust_vec_len, data_len), + 4 => wire__crate__api__simple__greet_impl(ptr, rust_vec_len, data_len), + 6 => wire__crate__api__ohos_smoke__reverse_bytes_impl(ptr, rust_vec_len, data_len), + 7 => wire__crate__api__ohos_smoke__transform_payload_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } // Section: rust2dart +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::ohos_smoke::OhosSmokePayload { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.code.into_into_dart().into_dart(), + self.label.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::ohos_smoke::OhosSmokePayload +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::ohos_smoke::OhosSmokePayload +{ + fn into_into_dart(self) -> crate::api::ohos_smoke::OhosSmokePayload { + self + } +} + +impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(format!("{:?}", self), serializer); + } +} + +impl SseEncode for StreamSink { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + unimplemented!("") + } +} + impl SseEncode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -196,6 +438,20 @@ impl SseEncode for String { } } +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -206,30 +462,31 @@ impl SseEncode for Vec { } } -impl SseEncode for u8 { +impl SseEncode for crate::api::ohos_smoke::OhosSmokePayload { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer.cursor.write_u8(self).unwrap(); + ::sse_encode(self.code, serializer); + ::sse_encode(self.label, serializer); } } -impl SseEncode for () { +impl SseEncode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u32::(self).unwrap(); + } } -impl SseEncode for i32 { +impl SseEncode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer.cursor.write_i32::(self).unwrap(); + serializer.cursor.write_u8(self).unwrap(); } } -impl SseEncode for bool { +impl SseEncode for () { // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer.cursor.write_u8(self as _).unwrap(); - } + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} } #[cfg(not(target_family = "wasm"))] diff --git a/frb_example/flutter_via_create/rust_builder/ohos/.gitignore b/frb_example/flutter_via_create/rust_builder/ohos/.gitignore index e2713a2779c..b2aa40502ce 100644 --- a/frb_example/flutter_via_create/rust_builder/ohos/.gitignore +++ b/frb_example/flutter_via_create/rust_builder/ohos/.gitignore @@ -3,4 +3,7 @@ /.preview /build /.cxx -/.test \ No newline at end of file +/.test +**/BuildProfile.ets +**/oh-package-lock.json5 +**/libs/** diff --git a/frb_example/flutter_via_create/rust_builder/ohos/src/main/cpp/CMakeLists.txt b/frb_example/flutter_via_create/rust_builder/ohos/src/main/cpp/CMakeLists.txt index c4a4c6410f5..d2e3e71b352 100644 --- a/frb_example/flutter_via_create/rust_builder/ohos/src/main/cpp/CMakeLists.txt +++ b/frb_example/flutter_via_create/rust_builder/ohos/src/main/cpp/CMakeLists.txt @@ -8,15 +8,17 @@ project(${PROJECT_NAME} LANGUAGES CXX) if (NOT DEFINED FLUTTER_TARGET_PLATFORM) if (OHOS_ARCH STREQUAL "arm64-v8a") set(FLUTTER_TARGET_PLATFORM "ohos-arm64") - elseif(OHOS_ARCH STREQUAL "armeabi-v7a") - set(FLUTTER_TARGET_PLATFORM "ohos-arm") elseif(OHOS_ARCH STREQUAL "x86_64") set(FLUTTER_TARGET_PLATFORM "ohos-x64") + elseif(OHOS_ARCH STREQUAL "armeabi-v7a") + message(FATAL_ERROR + "The current HarmonyOS Flutter/Hvigor toolchain does not support " + "the armeabi-v7a ABI. Use arm64-v8a or x86_64.") else() - message(WARNING "Unknown OHOS_ARCH: ${OHOS_ARCH}, defaulting to ohos-arm64") - set(FLUTTER_TARGET_PLATFORM "ohos-arm64") + message(FATAL_ERROR + "Unsupported OHOS_ARCH: ${OHOS_ARCH}. Use arm64-v8a or x86_64.") endif() - message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") + message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") endif() include("../../../../cargokit/cmake/cargokit.cmake") apply_cargokit(${PROJECT_NAME} ../../../../../rust rust_lib_flutter_via_create "") diff --git a/frb_example/flutter_via_integrate/ohos/.gitignore b/frb_example/flutter_via_integrate/ohos/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/ohos/AppScope/app.json5 b/frb_example/flutter_via_integrate/ohos/AppScope/app.json5 new file mode 100644 index 00000000000..570270ef1c1 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.flutter_via_integrate", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/element/string.json b/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 00000000000..a02d2b546f6 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "flutter_via_integrate" + } + ] +} diff --git a/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/media/app_icon.png b/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 00000000000..ce307a8827b Binary files /dev/null and b/frb_example/flutter_via_integrate/ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/frb_example/flutter_via_integrate/ohos/build-profile.json5 b/frb_example/flutter_via_integrate/ohos/build-profile.json5 new file mode 100644 index 00000000000..1c112b1a1be --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/build-profile.json5 @@ -0,0 +1,39 @@ + +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.1.0(18)", + "runtimeOS": "HarmonyOS", + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "profile" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/.gitignore b/frb_example/flutter_via_integrate/ohos/entry/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/ohos/entry/build-profile.json5 b/frb_example/flutter_via_integrate/ohos/entry/build-profile.json5 new file mode 100644 index 00000000000..6de31ee2243 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/build-profile.json5 @@ -0,0 +1,15 @@ + +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/hvigorfile.ts b/frb_example/flutter_via_integrate/ohos/entry/hvigorfile.ts new file mode 100644 index 00000000000..ff3bfe082b8 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/hvigorfile.ts @@ -0,0 +1,7 @@ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/frb_example/flutter_via_integrate/ohos/entry/oh-package.json5 b/frb_example/flutter_via_integrate/ohos/entry/oh-package.json5 new file mode 100644 index 00000000000..4e812b0f7c4 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/oh-package.json5 @@ -0,0 +1,10 @@ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {}, +} diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 00000000000..f85a65508a6 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,10 @@ + +import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos'; +import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'; + +export default class EntryAbility extends FlutterAbility { + configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + GeneratedPluginRegistrant.registerWith(flutterEngine) + } +} diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/pages/Index.ets b/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 00000000000..7bb6543fe7a --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,24 @@ + +import common from '@ohos.app.ability.common'; +import { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/module.json5 b/frb_example/flutter_via_integrate/ohos/entry/src/main/module.json5 new file mode 100644 index 00000000000..5bd7950ba53 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/module.json5 @@ -0,0 +1,40 @@ + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + {"name" : "ohos.permission.INTERNET"}, + ] + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/color.json b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000000..3c712962da3 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/string.json b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000000..016d5fc7af8 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "flutter_via_integrate" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/media/icon.png b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 00000000000..ce307a8827b Binary files /dev/null and b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/profile/main_pages.json b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000000..1898d94f58d --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/en_US/element/string.json b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000000..016d5fc7af8 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "flutter_via_integrate" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/zh_CN/element/string.json b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000000..cc1d8608a8a --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "flutter_via_integrate" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 00000000000..460ec4db147 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain',0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/List.test.ets b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000000..61e4b4033c2 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 00000000000..15d57a089c9 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,47 @@ +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; +import { AbilityConstant, Want } + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 00000000000..545843d3949 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,36 @@ + + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 00000000000..47fc89b4f05 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,50 @@ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/module.json5 b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000000..3b02a75a22d --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,37 @@ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/color.json b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 00000000000..3c712962da3 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/string.json b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 00000000000..65d8fa5a7cf --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/media/icon.png b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 00000000000..ce307a8827b Binary files /dev/null and b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 00000000000..b7e7343cacb --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/frb_example/flutter_via_integrate/ohos/hvigor/hvigor-config.json5 b/frb_example/flutter_via_integrate/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 00000000000..ad3ecab4a02 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,6 @@ + +{ + "modelVersion": "5.1.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/hvigorconfig.ts b/frb_example/flutter_via_integrate/ohos/hvigorconfig.ts new file mode 100644 index 00000000000..fc2b2086373 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/hvigorconfig.ts @@ -0,0 +1,4 @@ +import path from 'path' +import { injectNativeModules } from 'flutter-hvigor-plugin'; + +injectNativeModules(__dirname, path.dirname(__dirname)) \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/hvigorfile.ts b/frb_example/flutter_via_integrate/ohos/hvigorfile.ts new file mode 100644 index 00000000000..a64db460c84 --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/hvigorfile.ts @@ -0,0 +1,8 @@ +import path from 'path' +import { appTasks } from '@ohos/hvigor-ohos-plugin'; +import { flutterHvigorPlugin } from 'flutter-hvigor-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[flutterHvigorPlugin(path.dirname(__dirname))] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/ohos/oh-package.json5 b/frb_example/flutter_via_integrate/ohos/oh-package.json5 new file mode 100644 index 00000000000..6b770fd4e2a --- /dev/null +++ b/frb_example/flutter_via_integrate/ohos/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "modelVersion": "5.1.0", + "name": "flutter_via_integrate", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {}, + "devDependencies": { + "@ohos/hypium": "1.0.6" + } +} diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/.gitignore b/frb_example/flutter_via_integrate/rust_builder/ohos/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/Index.ets b/frb_example/flutter_via_integrate/rust_builder/ohos/Index.ets new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/build-profile.json5 b/frb_example/flutter_via_integrate/rust_builder/ohos/build-profile.json5 new file mode 100644 index 00000000000..310484b8c9e --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/build-profile.json5 @@ -0,0 +1,48 @@ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": [ + "arm64-v8a", + "x86_64" + ] + }, + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/consumer-rules.txt b/frb_example/flutter_via_integrate/rust_builder/ohos/consumer-rules.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/hvigorfile.ts b/frb_example/flutter_via_integrate/rust_builder/ohos/hvigorfile.ts new file mode 100644 index 00000000000..805c5d7f680 --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/obfuscation-rules.txt b/frb_example/flutter_via_integrate/rust_builder/ohos/obfuscation-rules.txt new file mode 100644 index 00000000000..1e7e54e150f --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/source-obfuscation + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/oh-package.json5 b/frb_example/flutter_via_integrate/rust_builder/ohos/oh-package.json5 new file mode 100644 index 00000000000..ea616343872 --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "rust_lib_flutter_via_integrate", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "librust_lib_flutter_via_integrate.so": "file:./src/main/cpp/types/librust_lib_flutter_via_integrate" + } +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/CMakeLists.txt b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000000..52cfb2c8060 --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/CMakeLists.txt @@ -0,0 +1,24 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "rust_lib_flutter_via_integrate") +set(TARGET_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}) +project(${PROJECT_NAME} LANGUAGES CXX) +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + if (OHOS_ARCH STREQUAL "arm64-v8a") + set(FLUTTER_TARGET_PLATFORM "ohos-arm64") + elseif(OHOS_ARCH STREQUAL "x86_64") + set(FLUTTER_TARGET_PLATFORM "ohos-x64") + elseif(OHOS_ARCH STREQUAL "armeabi-v7a") + message(FATAL_ERROR + "The current HarmonyOS Flutter/Hvigor toolchain does not support " + "the armeabi-v7a ABI. Use arm64-v8a or x86_64.") + else() + message(FATAL_ERROR + "Unsupported OHOS_ARCH: ${OHOS_ARCH}. Use arm64-v8a or x86_64.") + endif() + message(STATUS "Detected OpenHarmony platform: ${FLUTTER_TARGET_PLATFORM}") +endif() +include("../../../../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../../../../../rust rust_lib_flutter_via_integrate "") diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/types/librust_lib_flutter_via_integrate/Index.d.ts b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/types/librust_lib_flutter_via_integrate/Index.d.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/types/librust_lib_flutter_via_integrate/oh-package.json5 b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/types/librust_lib_flutter_via_integrate/oh-package.json5 new file mode 100644 index 00000000000..5d02be381ea --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/cpp/types/librust_lib_flutter_via_integrate/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "librust_lib_flutter_via_integrate.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/module.json5 b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/module.json5 new file mode 100644 index 00000000000..ddc699d2644 --- /dev/null +++ b/frb_example/flutter_via_integrate/rust_builder/ohos/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "rust_lib_flutter_via_integrate", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} diff --git a/frb_example/flutter_via_integrate/rust_builder/pubspec.yaml b/frb_example/flutter_via_integrate/rust_builder/pubspec.yaml index 9e982cf7965..cca01c6929c 100644 --- a/frb_example/flutter_via_integrate/rust_builder/pubspec.yaml +++ b/frb_example/flutter_via_integrate/rust_builder/pubspec.yaml @@ -32,3 +32,5 @@ flutter: ffiPlugin: true windows: ffiPlugin: true + ohos: + ffiPlugin: true diff --git a/frb_internal b/frb_internal index 336242ad718..7111dc841a0 100755 --- a/frb_internal +++ b/frb_internal @@ -3,6 +3,20 @@ SCRIPT=$(readlink -f "$0") SCRIPT_PATH=$(dirname "$SCRIPT") PACKAGE_CONFIG="$SCRIPT_PATH/tools/frb_internal/.dart_tool/package_config.json" +DART_BIN="${FRB_INTERNAL_DART:-dart}" + +if ! command -v "$DART_BIN" >/dev/null 2>&1; then + echo "Cannot find Dart executable: $DART_BIN" >&2 + echo "Set FRB_INTERNAL_DART to a Dart 3.13+ executable when the active Flutter SDK ships an older Dart." >&2 + exit 1 +fi + +if [[ "$DART_BIN" == */* ]]; then + DART_DIR=$(cd "$(dirname "$DART_BIN")" && pwd) + DART_BIN="$DART_DIR/$(basename "$DART_BIN")" + export PATH="$DART_DIR:$PATH" +fi + if [[ -f "$PACKAGE_CONFIG" ]]; then while IFS= read -r root_uri; do root_path="${root_uri#file://}" @@ -15,7 +29,7 @@ fi cd "$SCRIPT_PATH"/tools/frb_internal if [[ ! -f .dart_tool/package_config.json ]]; then - dart pub get + "$DART_BIN" pub get fi -exec dart run flutter_rust_bridge_internal "$@" +exec "$DART_BIN" run flutter_rust_bridge_internal "$@" diff --git a/tools/frb_internal/lib/src/makefile_dart/generate.dart b/tools/frb_internal/lib/src/makefile_dart/generate.dart index 3155b0b85ad..600c8d2385c 100644 --- a/tools/frb_internal/lib/src/makefile_dart/generate.dart +++ b/tools/frb_internal/lib/src/makefile_dart/generate.dart @@ -31,6 +31,13 @@ part 'generate.g.dart'; const _kRefreshCargoLockOrderingEnv = 'FRB_REFRESH_CARGO_LOCK_ORDERING'; +// `flutter create` does not include the OHOS platform when no `--platforms` +// argument is passed, even when the active Flutter fork supports OHOS. Keep +// the complete application platform set here so the integrate example gets a +// real OHOS application scaffold while retaining the other checked-in targets. +const _kFlutterCreatePlatformsWithOhos = + 'android,ios,linux,macos,ohos,web,windows'; + List> createCommands() { return [ SimpleConfigCommand( @@ -448,13 +455,21 @@ Future generateAppleScaffold() async { ); } + final extraArgs = integrateAppleScaffoldSourceOfTruthPackages() + .map( + (package) => + integrateDiffExclusionArgs(package, needCompareOhos: false), + ) + .where((args) => args.isNotEmpty) + .join(' '); + await wrapMaybeSetExitIfChangedRaw(true, () async { for (final package in integrateAppleScaffoldSourceOfTruthPackages()) { await generateRunFrbCodegenCommandIntegrate( generateAppleScaffoldPackageConfig(package), ); } - }); + }, extraArgs: extraArgs); } GenerateIntegratePackageConfig generateAppleScaffoldPackageConfig( @@ -514,7 +529,10 @@ Future generateRunFrbCodegenCommandIntegrate( case IntegrateExampleRecipe.integrateApp: await exec( - 'flutter create $packageName', + flutterCreateCommandForIntegrateForTesting( + packageName: packageName, + includeOhos: config.includeOhos, + ), relativePwd: 'frb_example', ); await executeFrbCodegen( @@ -584,6 +602,13 @@ Future generateRunFrbCodegenCommandIntegrate( ); } +String flutterCreateCommandForIntegrateForTesting({ + required String packageName, + required bool includeOhos, +}) => + 'flutter create $packageName' + '${includeOhos ? ' --platforms $_kFlutterCreatePlatformsWithOhos' : ''}'; + IntegrateExamplePackage _integrateRecipeForPackage(String package) { for (final config in kDartExampleIntegratePackageConfigs) { if (config.package == package) return config; diff --git a/tools/frb_internal/lib/src/makefile_dart/integrate_diff_exclusions.dart b/tools/frb_internal/lib/src/makefile_dart/integrate_diff_exclusions.dart index ed26aa7194c..13f42acee49 100644 --- a/tools/frb_internal/lib/src/makefile_dart/integrate_diff_exclusions.dart +++ b/tools/frb_internal/lib/src/makefile_dart/integrate_diff_exclusions.dart @@ -54,6 +54,7 @@ Map> _integrateSetExitIfChangedExcludedPathsByPackage({ package: [ '$package/example/macos/Flutter/Flutter-Debug.xcconfig', '$package/example/macos/Flutter/Flutter-Release.xcconfig', + '$package/example/pubspec.lock', ], }; @@ -66,6 +67,19 @@ List _flutterViaCreateExclusions( '$package/macos/Flutter/Flutter-Release.xcconfig', '$package/pubspec.lock', '$package/pubspec.yaml', + // The OHOS smoke API is an intentional addition to this checked-in + // example; it is not part of the generic Flutter create template. + if (package == 'frb_example/flutter_via_create') ...[ + '$package/lib/src/rust/api/ohos_smoke.dart', + '$package/lib/src/rust/frb_generated.dart', + '$package/lib/src/rust/frb_generated.io.dart', + '$package/lib/src/rust/frb_generated.web.dart', + '$package/rust/src/api/mod.rs', + '$package/rust/src/api/ohos_smoke.rs', + '$package/rust/src/frb_generated.rs', + '$package/ohos/entry/src/main/module.json5', + '$package/ohos/ohos_device_smoke_main.dart', + ], if (needCompareOhos) '$package/android/', if (needCompareOhos) '$package/macos/', if (needCompareOhos) '$package/windows/', diff --git a/tools/frb_internal/lib/src/makefile_dart/integrate_ohos_scaffold.dart b/tools/frb_internal/lib/src/makefile_dart/integrate_ohos_scaffold.dart index 0939822a423..398d5360402 100644 --- a/tools/frb_internal/lib/src/makefile_dart/integrate_ohos_scaffold.dart +++ b/tools/frb_internal/lib/src/makefile_dart/integrate_ohos_scaffold.dart @@ -10,7 +10,11 @@ const _kPreservedOhosScaffoldPaths = >{ 'rust_builder/pubspec.yaml', ], 'frb_example/flutter_via_create_native_assets': ['ohos'], - 'frb_example/flutter_via_integrate': ['ohos'], + 'frb_example/flutter_via_integrate': [ + 'ohos', + 'rust_builder/ohos', + 'rust_builder/pubspec.yaml', + ], }; typedef _OhosScaffoldPath = ({String relativePath, FileSystemEntityType type}); diff --git a/tools/frb_internal/lib/src/makefile_dart/ohos_build.dart b/tools/frb_internal/lib/src/makefile_dart/ohos_build.dart index c0246405de4..3f242006530 100644 --- a/tools/frb_internal/lib/src/makefile_dart/ohos_build.dart +++ b/tools/frb_internal/lib/src/makefile_dart/ohos_build.dart @@ -8,24 +8,38 @@ import 'package:path/path.dart' as path; import 'package:toml/toml.dart'; Future buildOhos(String package) async { - await _runOhosBuildPreflight(); - final hapBackup = stashOhosHapOutputForTesting( - Directory('${exec.pwd}$package/build/ohos/hap'), + final target = resolveOhosTargetForTesting( + Platform.environment['FRB_OHOS_TARGET_PLATFORM'], + ); + final buildMode = resolveOhosBuildModeForTesting( + Platform.environment['FRB_OHOS_BUILD_MODE'], + ); + await _runOhosBuildPreflight( + targetPlatform: target.flutterTargetPlatform, + buildMode: buildMode, + ); + final hapBackup = stashOhosHapOutputsForTesting( + ohosHapOutputDirectoriesForTesting('${exec.pwd}$package'), ); try { await exec( - 'flutter build hap --no-codesign --release --target-platform ohos-arm64 --verbose', + 'flutter build hap --no-codesign --$buildMode ' + '--target-platform ${target.flutterTargetPlatform} --verbose', relativePwd: package, ); - await _verifyOhosHapContainsRustLibrary(package); + await _verifyOhosHapContainsRustLibrary(package, abi: target.hapAbi); + normalizeOhosHapOutputForTesting('${exec.pwd}$package'); } catch (_) { - restoreOhosHapOutputForTesting(hapBackup); + restoreOhosHapOutputsForTesting(hapBackup); rethrow; } - hapBackup.backup?.deleteSync(recursive: true); + deleteOhosHapBackupsForTesting(hapBackup); } -Future _runOhosBuildPreflight() async { +Future _runOhosBuildPreflight({ + required String targetPlatform, + required String buildMode, +}) async { final sdkHome = Platform.environment['OHOS_SDK_HOME']; final errors = validateOhosSdkHomeForTesting( sdkHome: sdkHome, @@ -72,11 +86,60 @@ Future _runOhosBuildPreflight() async { print( 'OHOS build environment preflight passed: ' - 'SDK=$sdkHome, Flutter target=ohos-arm64, ' - 'Rust target=aarch64-unknown-linux-ohos, HAP ABI=arm64-v8a', + 'SDK=$sdkHome, Flutter target=$targetPlatform, build mode=$buildMode, ' + 'Rust target=${resolveOhosTargetForTesting(targetPlatform).rustTarget}, ' + 'HAP ABI=${resolveOhosTargetForTesting(targetPlatform).hapAbi}', ); } +String resolveOhosBuildModeForTesting(String? requestedBuildMode) { + final buildMode = requestedBuildMode?.trim().isNotEmpty == true + ? requestedBuildMode!.trim() + : 'release'; + switch (buildMode) { + case 'debug': + case 'profile': + case 'release': + return buildMode; + default: + throw ArgumentError.value( + buildMode, + 'buildMode', + 'expected debug, profile, or release', + ); + } +} + +({String flutterTargetPlatform, String rustTarget, String hapAbi}) +resolveOhosTargetForTesting(String? requestedTargetPlatform) { + final targetPlatform = requestedTargetPlatform?.trim().isNotEmpty == true + ? requestedTargetPlatform!.trim() + : 'ohos-arm64'; + return switch (targetPlatform) { + 'ohos-arm64' => ( + flutterTargetPlatform: 'ohos-arm64', + rustTarget: 'aarch64-unknown-linux-ohos', + hapAbi: 'arm64-v8a', + ), + 'ohos-x64' => ( + flutterTargetPlatform: 'ohos-x64', + rustTarget: 'x86_64-unknown-linux-ohos', + hapAbi: 'x86_64', + ), + 'ohos-arm' => throw ArgumentError.value( + targetPlatform, + 'targetPlatform', + 'ohos-arm is not supported by the current HarmonyOS HAP toolchain; ' + 'use ohos-arm64 or ohos-x64', + ), + _ => throw ArgumentError.value( + targetPlatform, + 'targetPlatform', + 'expected ohos-arm64 or ohos-x64', + ), + }; +} + List validateOhosSdkHomeForTesting({ required String? sdkHome, required bool isWindows, @@ -178,7 +241,34 @@ void restoreOhosHapOutputForTesting( } } -Future _verifyOhosHapContainsRustLibrary(String package) async { +({List<({Directory output, Directory? backup})> entries}) +stashOhosHapOutputsForTesting(Iterable outputs) => ( + entries: [for (final output in outputs) stashOhosHapOutputForTesting(output)], +); + +void restoreOhosHapOutputsForTesting( + ({List<({Directory output, Directory? backup})> entries}) state, +) { + for (final entry in state.entries) { + restoreOhosHapOutputForTesting(entry); + } +} + +void deleteOhosHapBackupsForTesting( + ({List<({Directory output, Directory? backup})> entries}) state, +) { + for (final entry in state.entries) { + final backup = entry.backup; + if (backup != null && backup.existsSync()) { + backup.deleteSync(recursive: true); + } + } +} + +Future _verifyOhosHapContainsRustLibrary( + String package, { + required String abi, +}) async { final packageDir = '${exec.pwd}$package'; final cargoTomlPath = ohosRustCargoTomlPathForTesting( packageDir: packageDir, @@ -186,18 +276,20 @@ Future _verifyOhosHapContainsRustLibrary(String package) async { ); final cargoToml = File(cargoTomlPath).readAsStringSync(); final expectedLibrary = ohosRustLibraryNameForTesting(cargoToml); - final hapDirectory = Directory('$packageDir/build/ohos/hap'); - final hapFiles = hapDirectory.existsSync() - ? hapDirectory + final hapDirectories = ohosHapOutputDirectoriesForTesting(packageDir); + final hapFiles = [ + for (final hapDirectory in hapDirectories) + if (hapDirectory.existsSync()) + ...hapDirectory .listSync(recursive: true) .whereType() - .where((file) => file.path.endsWith('.hap')) - .toList() - : []; + .where((file) => file.path.endsWith('.hap')), + ]; if (hapFiles.isEmpty) { throw StateError( - 'OHOS build produced no HAP files in ${hapDirectory.path}', + 'OHOS build produced no HAP files in ' + '${hapDirectories.map((directory) => directory.path).join(', ')}', ); } @@ -208,25 +300,55 @@ Future _verifyOhosHapContainsRustLibrary(String package) async { validateOhosHapRustLibrariesForTesting( entriesByHap, expectedLibrary: expectedLibrary, + abi: abi, ); } +List ohosHapOutputDirectoriesForTesting(String packageDir) => [ + Directory(path.join(packageDir, 'build', 'ohos', 'hap')), + // Recent Hvigor versions keep the final HAP under the entry module when + // Flutter does not mirror it into build/ohos/hap. + Directory(path.join(packageDir, 'ohos', 'entry', 'build')), +]; + +Directory normalizeOhosHapOutputForTesting(String packageDir) { + final directories = ohosHapOutputDirectoriesForTesting(packageDir); + final canonical = directories.first; + final existingHaps = [ + for (final directory in directories.skip(1)) + if (directory.existsSync()) + ...directory + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.hap')), + ]; + if (existingHaps.isEmpty) return canonical; + + canonical.createSync(recursive: true); + for (final hap in existingHaps) { + hap.copySync(path.join(canonical.path, path.basename(hap.path))); + } + return canonical; +} + void validateOhosHapRustLibrariesForTesting( Map> entriesByHap, { required String expectedLibrary, + String abi = 'arm64-v8a', }) { final invalidHaps = entriesByHap.entries .where( (entry) => !ohosHapContainsRustLibraryForTesting( entry.value, expectedLibrary: expectedLibrary, + abi: abi, ), ) .map((entry) => entry.key) .toList(); if (invalidHaps.isNotEmpty) { throw StateError( - 'OHOS HAPs do not contain $expectedLibrary for arm64-v8a: ' + 'OHOS HAPs do not contain $expectedLibrary for $abi: ' '${invalidHaps.join(', ')}', ); } diff --git a/tools/frb_internal/lib/src/makefile_dart/ohos_hap.dart b/tools/frb_internal/lib/src/makefile_dart/ohos_hap.dart index 0f934a0ccab..2cb1a9eef49 100644 --- a/tools/frb_internal/lib/src/makefile_dart/ohos_hap.dart +++ b/tools/frb_internal/lib/src/makefile_dart/ohos_hap.dart @@ -126,7 +126,7 @@ ohosArchiveListerCandidatesForTesting() => const [ bool ohosHapContainsRustLibraryForTesting( Iterable entries, { required String expectedLibrary, + String abi = 'arm64-v8a', }) => entries.any( - (entry) => - entry.trim().replaceAll('\\', '/') == 'libs/arm64-v8a/$expectedLibrary', + (entry) => entry.trim().replaceAll('\\', '/') == 'libs/$abi/$expectedLibrary', ); diff --git a/tools/frb_internal/test/generate_test.dart b/tools/frb_internal/test/generate_test.dart new file mode 100644 index 00000000000..30c212c6b44 --- /dev/null +++ b/tools/frb_internal/test/generate_test.dart @@ -0,0 +1,25 @@ +import 'package:flutter_rust_bridge_internal/src/makefile_dart/generate.dart'; +import 'package:test/test.dart'; + +void main() { + test('integrate OHOS generation asks Flutter for the OHOS scaffold', () { + expect( + flutterCreateCommandForIntegrateForTesting( + packageName: 'flutter_via_integrate', + includeOhos: true, + ), + 'flutter create flutter_via_integrate ' + '--platforms android,ios,linux,macos,ohos,web,windows', + ); + }); + + test('ordinary integrate generation keeps Flutter defaults', () { + expect( + flutterCreateCommandForIntegrateForTesting( + packageName: 'flutter_via_integrate', + includeOhos: false, + ), + 'flutter create flutter_via_integrate', + ); + }); +} diff --git a/tools/frb_internal/test/integrate_diff_exclusions_test.dart b/tools/frb_internal/test/integrate_diff_exclusions_test.dart index 04a1d5d030d..38b85732436 100644 --- a/tools/frb_internal/test/integrate_diff_exclusions_test.dart +++ b/tools/frb_internal/test/integrate_diff_exclusions_test.dart @@ -10,6 +10,15 @@ void main() { "':(exclude)$package/macos/Flutter/Flutter-Release.xcconfig' " "':(exclude)$package/pubspec.lock' " "':(exclude)$package/pubspec.yaml' " + "':(exclude)$package/lib/src/rust/api/ohos_smoke.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.io.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.web.dart' " + "':(exclude)$package/rust/src/api/mod.rs' " + "':(exclude)$package/rust/src/api/ohos_smoke.rs' " + "':(exclude)$package/rust/src/frb_generated.rs' " + "':(exclude)$package/ohos/entry/src/main/module.json5' " + "':(exclude)$package/ohos/ohos_device_smoke_main.dart' " "':(exclude)$package/ohos/' " "':(exclude)$package/rust_builder/ohos/' " "':(exclude)$package/rust_builder/pubspec.yaml'", @@ -41,6 +50,15 @@ void main() { "':(exclude)$package/macos/Flutter/Flutter-Release.xcconfig' " "':(exclude)$package/pubspec.lock' " "':(exclude)$package/pubspec.yaml' " + "':(exclude)$package/lib/src/rust/api/ohos_smoke.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.io.dart' " + "':(exclude)$package/lib/src/rust/frb_generated.web.dart' " + "':(exclude)$package/rust/src/api/mod.rs' " + "':(exclude)$package/rust/src/api/ohos_smoke.rs' " + "':(exclude)$package/rust/src/frb_generated.rs' " + "':(exclude)$package/ohos/entry/src/main/module.json5' " + "':(exclude)$package/ohos/ohos_device_smoke_main.dart' " "':(exclude)$package/android/' " "':(exclude)$package/macos/' " "':(exclude)$package/windows/'", @@ -70,7 +88,8 @@ void main() { expect( integrateDiffExclusionArgsForTesting(package, needCompareOhos: false), "':(exclude)$package/example/macos/Flutter/Flutter-Debug.xcconfig' " - "':(exclude)$package/example/macos/Flutter/Flutter-Release.xcconfig'", + "':(exclude)$package/example/macos/Flutter/Flutter-Release.xcconfig' " + "':(exclude)$package/example/pubspec.lock'", reason: package, ); } diff --git a/tools/frb_internal/test/integrate_ohos_scaffold_test.dart b/tools/frb_internal/test/integrate_ohos_scaffold_test.dart index 3e8d2e1ba13..8f0abec816c 100644 --- a/tools/frb_internal/test/integrate_ohos_scaffold_test.dart +++ b/tools/frb_internal/test/integrate_ohos_scaffold_test.dart @@ -18,6 +18,8 @@ void main() { ); expect(preservedOhosScaffoldPaths('frb_example/flutter_via_integrate'), [ 'ohos', + 'rust_builder/ohos', + 'rust_builder/pubspec.yaml', ]); expect(preservedOhosScaffoldPaths('frb_example/flutter_package'), isEmpty); }); @@ -71,8 +73,13 @@ void main() { final original = Directory('${tempDir.path}/original'); final generated = Directory('${tempDir.path}/generated'); Directory('${original.path}/ohos').createSync(recursive: true); + Directory('${original.path}/rust_builder/ohos').createSync(recursive: true); File('${original.path}/ohos/ohos_device_smoke_main.dart') .writeAsStringSync('void main() {}'); + File('${original.path}/rust_builder/ohos/CMakeLists.txt') + .writeAsStringSync('cmake'); + File('${original.path}/rust_builder/pubspec.yaml') + .writeAsStringSync('plugin:\n platforms:\n ohos:\n'); Directory(generated.path).createSync(recursive: true); await preserveCheckedInOhosScaffold( @@ -86,6 +93,15 @@ void main() { .readAsStringSync(), 'void main() {}', ); + expect( + File('${generated.path}/rust_builder/ohos/CMakeLists.txt') + .readAsStringSync(), + 'cmake', + ); + expect( + File('${generated.path}/rust_builder/pubspec.yaml').readAsStringSync(), + 'plugin:\n platforms:\n ohos:\n', + ); }); test( diff --git a/tools/frb_internal/test/ohos_build_test.dart b/tools/frb_internal/test/ohos_build_test.dart index 1ffedc93d69..cf7f2109ef3 100644 --- a/tools/frb_internal/test/ohos_build_test.dart +++ b/tools/frb_internal/test/ohos_build_test.dart @@ -4,6 +4,61 @@ import 'package:flutter_rust_bridge_internal/src/makefile_dart/ohos_build.dart'; import 'package:test/test.dart'; void main() { + test('OHOS target mapping selects Flutter, Rust, and HAP architectures', () { + expect(resolveOhosTargetForTesting(null).hapAbi, 'arm64-v8a'); + expect( + resolveOhosTargetForTesting('ohos-x64').rustTarget, + 'x86_64-unknown-linux-ohos', + ); + expect(resolveOhosTargetForTesting('ohos-x64').hapAbi, 'x86_64'); + expect(() => resolveOhosTargetForTesting('ohos-arm'), throwsArgumentError); + expect( + () => resolveOhosTargetForTesting('ohos-invalid'), + throwsArgumentError, + ); + }); + + test('OHOS build mode defaults to release and accepts supported modes', () { + expect(resolveOhosBuildModeForTesting(null), 'release'); + expect(resolveOhosBuildModeForTesting('debug'), 'debug'); + expect(resolveOhosBuildModeForTesting('profile'), 'profile'); + expect(resolveOhosBuildModeForTesting(' release '), 'release'); + expect( + () => resolveOhosBuildModeForTesting('staging'), + throwsArgumentError, + ); + }); + + test('OHOS HAP validation searches Flutter and Hvigor output locations', () { + final directories = ohosHapOutputDirectoriesForTesting('/tmp/example'); + expect(directories.first.path, endsWith('/build/ohos/hap')); + expect(directories.last.path, endsWith('/ohos/entry/build')); + }); + + test('OHOS HAP output is normalized for artifact copying', () { + final temporaryDirectory = Directory.systemTemp.createTempSync( + 'frb_ohos_hap_normalize_test_', + ); + try { + final entryBuild = Directory( + '${temporaryDirectory.path}/ohos/entry/build/outputs', + )..createSync(recursive: true); + File('${entryBuild.path}/entry-default.hap').writeAsStringSync('hap'); + + final canonical = normalizeOhosHapOutputForTesting( + temporaryDirectory.path, + ); + + expect(canonical.path, endsWith('/build/ohos/hap')); + expect( + File('${canonical.path}/entry-default.hap').readAsStringSync(), + 'hap', + ); + } finally { + temporaryDirectory.deleteSync(recursive: true); + } + }); + test('OHOS Rust library name follows Cargo crate naming rules', () { expect( ohosRustLibraryNameForTesting(''' @@ -108,6 +163,50 @@ crate-type = ["cdylib"] } }); + test('OHOS HAP backup covers all supported output locations', () { + final temporaryDirectory = Directory.systemTemp.createTempSync( + 'frb_ohos_hap_multi_backup_test_', + ); + try { + final firstOutput = Directory('${temporaryDirectory.path}/flutter/hap') + ..createSync(recursive: true); + File('${firstOutput.path}/previous.hap').writeAsStringSync('previous'); + final secondOutput = Directory('${temporaryDirectory.path}/entry/build') + ..createSync(recursive: true); + File('${secondOutput.path}/previous.hap').writeAsStringSync('previous'); + + final backup = stashOhosHapOutputsForTesting([firstOutput, secondOutput]); + expect(firstOutput.existsSync(), isFalse); + expect(secondOutput.existsSync(), isFalse); + + firstOutput.createSync(recursive: true); + secondOutput.createSync(recursive: true); + File('${firstOutput.path}/failed.hap').writeAsStringSync('failed'); + File('${secondOutput.path}/failed.hap').writeAsStringSync('failed'); + restoreOhosHapOutputsForTesting(backup); + + expect( + File('${firstOutput.path}/previous.hap').readAsStringSync(), + 'previous', + ); + expect( + File('${secondOutput.path}/previous.hap').readAsStringSync(), + 'previous', + ); + expect(File('${firstOutput.path}/failed.hap').existsSync(), isFalse); + expect(File('${secondOutput.path}/failed.hap').existsSync(), isFalse); + deleteOhosHapBackupsForTesting(backup); + expect( + backup.entries.every( + (entry) => entry.backup == null || !entry.backup!.existsSync(), + ), + isTrue, + ); + } finally { + temporaryDirectory.deleteSync(recursive: true); + } + }); + test('OHOS SDK preflight accepts a complete native SDK directory', () { const sdkHome = '/opt/ohos/18/native'; final existingPaths = { diff --git a/tools/frb_internal/test/ohos_hap_test.dart b/tools/frb_internal/test/ohos_hap_test.dart index 3386bf7ce3b..eb8716f1b54 100644 --- a/tools/frb_internal/test/ohos_hap_test.dart +++ b/tools/frb_internal/test/ohos_hap_test.dart @@ -2,33 +2,38 @@ import 'package:flutter_rust_bridge_internal/src/makefile_dart/ohos_hap.dart'; import 'package:test/test.dart'; void main() { - test('OHOS HAP validation requires the arm64 Rust dynamic library', () { - expect( - ohosHapContainsRustLibraryForTesting([ - 'libs/arm64-v8a/libflutter.so', - 'libs/arm64-v8a/librust_lib_example.so', - ], expectedLibrary: 'librust_lib_example.so'), - isTrue, - ); - expect( - ohosHapContainsRustLibraryForTesting([ - 'libs/x86_64/librust_lib_example.so', - ], expectedLibrary: 'librust_lib_example.so'), - isFalse, - ); - expect( - ohosHapContainsRustLibraryForTesting([ - 'assets/arm64-v8a/librust_lib_example.so', - ], expectedLibrary: 'librust_lib_example.so'), - isFalse, - ); - expect( - ohosHapContainsRustLibraryForTesting([ - 'libs/arm64-v8a/libflutter.so', - ], expectedLibrary: 'librust_lib_example.so'), - isFalse, - ); - }); + test( + 'OHOS HAP validation requires the Rust library for the selected ABI', + () { + expect( + ohosHapContainsRustLibraryForTesting([ + 'libs/arm64-v8a/libflutter.so', + 'libs/arm64-v8a/librust_lib_example.so', + ], expectedLibrary: 'librust_lib_example.so'), + isTrue, + ); + expect( + ohosHapContainsRustLibraryForTesting( + ['libs/x86_64/librust_lib_example.so'], + expectedLibrary: 'librust_lib_example.so', + abi: 'x86_64', + ), + isTrue, + ); + expect( + ohosHapContainsRustLibraryForTesting([ + 'assets/arm64-v8a/librust_lib_example.so', + ], expectedLibrary: 'librust_lib_example.so'), + isFalse, + ); + expect( + ohosHapContainsRustLibraryForTesting([ + 'libs/arm64-v8a/libflutter.so', + ], expectedLibrary: 'librust_lib_example.so'), + isFalse, + ); + }, + ); test('OHOS HAP inspection falls back from JDK jar to unzip', () { final candidates = ohosArchiveListerCandidatesForTesting(); diff --git a/tools/manual_tests/ohos-device-smoke.md b/tools/manual_tests/ohos-device-smoke.md index 559e071469f..8b6983e9b3a 100644 --- a/tools/manual_tests/ohos-device-smoke.md +++ b/tools/manual_tests/ohos-device-smoke.md @@ -4,7 +4,8 @@ Verify that a signed arm64 HAP produced from the CargoKit create example can be installed on an OHOS/HarmonyOS device, start `EntryAbility`, load the packaged -Rust library, complete a Dart-to-Rust call, and clean up the test application. +Rust library, exercise representative FRB codecs and call modes, and clean up +the test application. ## Source @@ -33,7 +34,8 @@ entrypoint. Run before claiming real-device coverage in a PR or release. - OS: macOS, Linux, or Windows supported by the selected OHOS Flutter SDK. - Flutter: an OHOS-enabled Flutter version compatible with this checkout. -- Dart: the version bundled with that Flutter SDK. +- Dart: the version bundled with that Flutter SDK for Flutter commands; Dart + 3.13 or newer for repository `frb_internal` commands. - Rust: stable with the `aarch64-unknown-linux-ohos` target installed. - Device or simulator: arm64 OHOS/HarmonyOS device reachable through `hdc`. - Browser or external service: not required. @@ -50,6 +52,24 @@ Set `OHOS_SDK_HOME` to the installed native SDK directory. Configure a dedicated bundle name in `frb_example/flutter_via_create/ohos/AppScope/app.json5`, then configure local debug signing without committing the signing material. +If the OHOS Flutter fork bundles an older Dart SDK, point repository commands at +a separate Dart 3.13 or newer executable while keeping the OHOS Flutter SDK first +on `PATH`: + +```bash +export FRB_INTERNAL_DART=/absolute/path/to/dart-3.13/bin/dart +``` + +Use the Hvigor wrapper and Node runtime from the same DevEco Studio installation +for the build. Put that Hvigor directory before any standalone command-line-tools +directory in `PATH`: + +```bash +export HVIGOR_HOME=/Applications/DevEco-Studio.app/Contents/tools/hvigor +export NODE_HOME=/Applications/DevEco-Studio.app/Contents/tools/node +export PATH="$HVIGOR_HOME/bin:$NODE_HOME/bin:$PATH" +``` + ## Test Data - Input: the signed debug HAP built from `frb_example/flutter_via_create`. diff --git a/website/docs/guides/miscellaneous/harmony-os.md b/website/docs/guides/miscellaneous/harmony-os.md index c74f6379277..eb687771b6a 100644 --- a/website/docs/guides/miscellaneous/harmony-os.md +++ b/website/docs/guides/miscellaneous/harmony-os.md @@ -36,9 +36,13 @@ The CI workflow pins these values instead of following the latest OHOS SDK or Flutter fork automatically. Other toolchain combinations can work, but are not release-gated yet. x86_64, armv7, profile, Native Assets, plugin consumption, and CI-controlled real-device execution remain experimental or unverified until -their test matrix is added. A dedicated test entrypoint provides a deterministic -synchronous Dart-to-Rust device smoke marker; broader FRB API coverage is not -yet a release gate. +their test matrix is added. A dedicated test entrypoint exercises representative +FRB values on one signed device build; this broader API smoke is not yet a +release gate. + +Use the CargoKit backend for OHOS builds. The Native Assets path still depends +on `native_toolchain_rust` target mappings that do not cover OHOS, so selecting +that backend does not provide a supported OHOS build today. ## `OHOS_SDK_HOME` @@ -91,6 +95,33 @@ available or that `unzip` can be used as a HAP inspection fallback. The successful preflight log also prints the Flutter target, Rust target, HAP ABI, and selected native SDK path. +The OHOS Flutter fork may ship an older Dart SDK than the repository's internal +development tools. Keep the OHOS Flutter executable first on `PATH` for Flutter +builds, and set `FRB_INTERNAL_DART` to a separate Dart 3.13 or newer executable +when running `frb_internal`: + +```shell +export FRB_INTERNAL_DART=/opt/dart/3.13/bin/dart +./frb_internal ohos-device-smoke --help +``` + +If `FRB_INTERNAL_DART` is unset, `frb_internal` uses the `dart` executable on +`PATH` as before. + +When DevEco Studio is installed on macOS, use its bundled Hvigor wrapper and +Node runtime for Hvigor and `ohpm`. Put the matching Hvigor directory before a +standalone command-line-tools installation; mixing Hvigor releases can fail +with errors such as `WithParamReplacement is not a function`. Adjust these +paths for your installation: + +```shell +export HVIGOR_HOME=/Applications/DevEco-Studio.app/Contents/tools/hvigor +export NODE_HOME=/Applications/DevEco-Studio.app/Contents/tools/node +export PATH="$HVIGOR_HOME/bin:$NODE_HOME/bin:$PATH" +command -v hvigorw +hvigorw --version +``` + You can perform the equivalent core checks locally before a build: ```shell @@ -212,11 +243,12 @@ When more than one device is connected, select one explicitly: --device-id DEVICE_UDID ``` -The dedicated entrypoint emits `FRB_OHOS_SMOKE_RESULT=PASS` only after its -synchronous `greet` call returns the expected value. For a broader device test -fixture, pass its deterministic marker with `--expected-log`. The command also -checks that `--bundle` matches the HAP metadata before installation. Logs are -saved under `target/ohos_device_smoke` by default. Use a dedicated bundle name +The dedicated entrypoint emits `FRB_OHOS_SMOKE_RESULT=PASS` only after it +exercises the synchronous string call, byte-array and struct codecs, an async +call, a fallible call, and a stream. For a broader device test fixture, pass its +deterministic marker with `--expected-log`. The command also checks that +`--bundle` matches the HAP metadata before installation. Logs are saved under +`target/ohos_device_smoke` by default. Use a dedicated bundle name whose application is not already installed on the device; the command intentionally aborts instead of overwriting user application data. To use a dedicated name, change `bundleName` in `ohos/AppScope/app.json5` before building @@ -227,13 +259,43 @@ signature in DevEco Studio or sign the HAP through your existing secure signing workflow before running it. Do not commit `.p12`, provision profiles, passwords, or generated signing configuration. +The build gate defaults to `ohos-arm64` and checks the matching `arm64-v8a` +Rust library. To build another supported target, set +`FRB_OHOS_TARGET_PLATFORM` before running the repository build command: + +```shell +FRB_OHOS_TARGET_PLATFORM=ohos-x64 ./frb_internal build-flutter \ + --package frb_example/flutter_via_create \ + --target ohos +``` + +The supported target mapping is `ohos-arm64`/`arm64-v8a` and +`ohos-x64`/`x86_64`. The current HarmonyOS Hvigor toolchain rejects the +`armeabi-v7a` ABI, so `ohos-arm` is rejected before Flutter starts instead of +failing late during HAP configuration. Keep the existing CMake mapping only as +forward-compatible scaffolding for a future toolchain that supports armv7. + +The build gate uses release mode by default. To validate the same packaging +checks against a different Flutter mode, set `FRB_OHOS_BUILD_MODE` to `debug`, +`profile`, or `release`: + +```shell +FRB_OHOS_BUILD_MODE=profile ./frb_internal build-flutter \ + --package frb_example/flutter_via_create \ + --target ohos +``` + +The mode value is validated before Flutter starts so a typo cannot silently +produce a different artifact. + ## HarmonyOS PC and OpenHarmony device types Some Flutter OHOS templates generate an entry module that only declares the `phone` device type. On an OpenHarmony PC or 2-in-1 SDK this can produce an empty -system-capability intersection during the Hvigor build. If the build reports -that `phone` is unsupported, update the application entry module's -`deviceTypes` for the target product, for example: +system-capability intersection during the Hvigor build. The checked-in +quickstart entry module includes the broad `default` and `tablet` types. For an +existing application, update the application entry module's `deviceTypes` for +the target product when the generated `phone` type is unsupported, for example: ```json5 "deviceTypes": [