From b32a8ef0e5507774fb0da64cb17859818e309f59 Mon Sep 17 00:00:00 2001 From: Gregory Conrad Date: Sat, 27 Sep 2025 17:56:39 -0400 Subject: [PATCH] ci: add detailed examples and tests --- .github/workflows/build.yml | 182 ++++++++---------- .gitignore | 4 +- Cargo.toml | 3 + examples/dart_only/hook/build.dart | 11 ++ examples/dart_only/lib/dart_only_example.dart | 1 + examples/dart_only/lib/src/ffi.g.dart | 11 ++ examples/dart_only/pubspec.yaml | 20 ++ examples/dart_only/rust/Cargo.toml | 10 + examples/dart_only/rust/bindings.h | 10 + examples/dart_only/rust/build.rs | 32 +++ examples/dart_only/rust/c/c_add.c | 5 + examples/dart_only/rust/c/c_add.h | 8 + examples/dart_only/rust/rust-toolchain.toml | 1 + examples/dart_only/rust/src/lib.rs | 16 ++ .../test/dart_only_example_test.dart | 8 + examples/dart_only/tool/ffigen.dart | 12 ++ examples/flutter/hook/build.dart | 11 ++ .../flutter/integration_test/widget_test.dart | 24 +++ examples/flutter/lib/main.dart | 68 +++++++ examples/flutter/lib/src/ffi.g.dart | 14 ++ examples/flutter/pubspec.yaml | 28 +++ examples/flutter/rust/Cargo.toml | 9 + examples/flutter/rust/bindings.h | 12 ++ examples/flutter/rust/build.rs | 22 +++ examples/flutter/rust/rust-toolchain.toml | 1 + examples/flutter/rust/src/lib.rs | 37 ++++ examples/flutter/test/widget_test.dart | 24 +++ examples/flutter/tool/ffigen.dart | 16 ++ pubspec.lock | 144 ++++++++++++++ pubspec.yaml | 20 ++ rust-toolchain.toml | 28 +++ 31 files changed, 692 insertions(+), 100 deletions(-) create mode 100644 Cargo.toml create mode 100644 examples/dart_only/hook/build.dart create mode 100644 examples/dart_only/lib/dart_only_example.dart create mode 100644 examples/dart_only/lib/src/ffi.g.dart create mode 100644 examples/dart_only/pubspec.yaml create mode 100644 examples/dart_only/rust/Cargo.toml create mode 100644 examples/dart_only/rust/bindings.h create mode 100644 examples/dart_only/rust/build.rs create mode 100644 examples/dart_only/rust/c/c_add.c create mode 100644 examples/dart_only/rust/c/c_add.h create mode 120000 examples/dart_only/rust/rust-toolchain.toml create mode 100644 examples/dart_only/rust/src/lib.rs create mode 100644 examples/dart_only/test/dart_only_example_test.dart create mode 100644 examples/dart_only/tool/ffigen.dart create mode 100644 examples/flutter/hook/build.dart create mode 100644 examples/flutter/integration_test/widget_test.dart create mode 100644 examples/flutter/lib/main.dart create mode 100644 examples/flutter/lib/src/ffi.g.dart create mode 100644 examples/flutter/pubspec.yaml create mode 100644 examples/flutter/rust/Cargo.toml create mode 100644 examples/flutter/rust/bindings.h create mode 100644 examples/flutter/rust/build.rs create mode 120000 examples/flutter/rust/rust-toolchain.toml create mode 100644 examples/flutter/rust/src/lib.rs create mode 100644 examples/flutter/test/widget_test.dart create mode 100644 examples/flutter/tool/ffigen.dart create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index beda071..e99e673 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,13 +34,13 @@ jobs: direnv allow . direnv export gha > "$GITHUB_ENV" - # # Rust - # - name: Rust lint - # run: cargo clippy -- -D warnings - # - name: Rust format - # run: cargo fmt --all --check - # - name: Rust tests - # run: cargo test + # Rust + - name: Rust lint + run: cargo clippy -- -D warnings + - name: Rust format + run: cargo fmt --all --check + - name: Rust tests + run: cargo test # Dart/Flutter - name: Dart lint @@ -50,98 +50,84 @@ jobs: - name: Dart tests run: melos run test - # macos_integration_test: - # runs-on: macos-latest - # steps: - # - uses: actions/checkout@v5 - # - uses: subosito/flutter-action@v2 - # with: - # channel: beta # TODO remove - # - name: Create macOS boilerplate - # working-directory: packages/flutter_mimir/example - # run: flutter create . --platforms=macos - # - name: Disable macOS App Sandbox - # working-directory: packages/flutter_mimir/example - # run: /usr/libexec/PlistBuddy -c "Delete :com.apple.security.app-sandbox" macos/Runner/DebugProfile.entitlements - # - name: Run Flutter integration tests - # working-directory: packages/flutter_mimir/example - # run: flutter test -d macos integration_test + macos_integration_test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + - uses: subosito/flutter-action@v2 + with: + channel: beta # TODO remove + - uses: bluefireteam/melos-action@v3 + - run: melos run init-integration-test-boilerplate + - run: melos run integration-test -- -d macos - # windows_integration_test: - # runs-on: windows-latest - # steps: - # - uses: actions/checkout@v5 - # - uses: subosito/flutter-action@v2 - # with: - # channel: beta # TODO remove - # - name: Create Windows boilerplate - # working-directory: packages/flutter_mimir/example - # run: flutter create . --platforms=windows - # - name: Run Flutter integration tests - # working-directory: packages/flutter_mimir/example - # run: flutter test -d windows integration_test + windows_integration_test: + runs-on: windows-latest + steps: + - uses: actions/checkout@v5 + - uses: subosito/flutter-action@v2 + with: + channel: beta # TODO remove + - uses: bluefireteam/melos-action@v3 + - run: melos run init-integration-test-boilerplate + - run: melos run integration-test -- -d windows - # linux_integration_test: - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v5 - # - uses: subosito/flutter-action@v2 - # with: - # channel: beta # TODO remove - # - uses: pyvista/setup-headless-display-action@v3 - # - name: Install dependencies for flutter integration test - # run: sudo apt update && sudo apt-get install -y libglu1-mesa ninja-build clang cmake pkg-config libgtk-3-dev liblzma-dev - # - name: Create Linux boilerplate - # working-directory: packages/flutter_mimir/example - # run: flutter create . --platforms=linux - # - name: Run Flutter integration tests - # working-directory: packages/flutter_mimir/example - # run: flutter test -d linux integration_test + linux_integration_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: subosito/flutter-action@v2 + with: + channel: beta # TODO remove + - uses: bluefireteam/melos-action@v3 + - uses: pyvista/setup-headless-display-action@v3 + - name: Install dependencies for flutter integration test + run: sudo apt update && sudo apt-get install -y libglu1-mesa ninja-build clang cmake pkg-config libgtk-3-dev liblzma-dev + - run: melos run init-integration-test-boilerplate + - run: melos run integration-test -- -d linux - # ios_integration_test: - # runs-on: macos-latest - # steps: - # - uses: actions/checkout@v5 - # - uses: subosito/flutter-action@v2 - # with: - # channel: beta # TODO remove - # - name: Start iOS Simulator - # uses: futureware-tech/simulator-action@v4 - # id: start_simulator - # with: - # wait_for_boot: true - # - name: Create iOS boilerplate - # working-directory: packages/flutter_mimir/example - # run: flutter create . --platforms=ios - # - name: Run Flutter integration tests # NOTE: we often get timeouts otherwise - # uses: Wandalen/wretry.action@v3 - # with: - # attempt_limit: 5 - # current_path: packages/flutter_mimir/example - # command: flutter test -d ${{ steps.start_simulator.outputs.udid }} integration_test + ios_integration_test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + - uses: subosito/flutter-action@v2 + with: + channel: beta # TODO remove + - uses: bluefireteam/melos-action@v3 + - name: Start iOS Simulator + uses: futureware-tech/simulator-action@v4 + id: start_simulator + with: + wait_for_boot: true + - run: melos run init-integration-test-boilerplate + # NOTE: we often get timeouts without a couple retries due to flakey macOS runners + - name: Run Flutter integration tests + uses: Wandalen/wretry.action@v3 + with: + attempt_limit: 5 + current_path: examples/flutter + command: melos run integration-test -- -d ${{ steps.start_simulator.outputs.udid }} - # android_integration_test: - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v5 - # - uses: subosito/flutter-action@v2 - # with: - # channel: beta # TODO remove - # - name: Create Android boilerplate - # working-directory: packages/flutter_mimir/example - # run: flutter create . --platforms=android - # - name: Enable KVM for Android emulator - # run: | - # echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - # sudo udevadm control --reload-rules - # sudo udevadm trigger --name-match=kvm - # - name: Run Flutter integration tests - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 35 - # target: default - # arch: x86_64 - # ram-size: 1024M - # disk-size: 2048M - # working-directory: packages/flutter_mimir/example - # script: flutter test -d `flutter devices | grep android | tr ' ' '\n' | grep emulator-` integration_test + android_integration_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: subosito/flutter-action@v2 + with: + channel: beta # TODO remove + - uses: bluefireteam/melos-action@v3 + - name: Enable KVM for Android emulator + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - run: melos run init-integration-test-boilerplate + - name: Run Flutter integration tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + target: default + arch: x86_64 + ram-size: 1024M + disk-size: 2048M + script: melos run integration-test -- -d `flutter devices | grep android | tr ' ' '\n' | grep emulator-` diff --git a/.gitignore b/.gitignore index 8beec5b..3a3dedc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,8 @@ pubspec.lock **/doc/api/ .dart_tool/ .packages -/build/ -/coverage/ +build/ +coverage/ .pub-cache/ .pub/ .flutter-plugins diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f0ca56f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "3" +members = ["examples/dart_only/rust", "examples/flutter/rust"] diff --git a/examples/dart_only/hook/build.dart b/examples/dart_only/hook/build.dart new file mode 100644 index 0000000..138390c --- /dev/null +++ b/examples/dart_only/hook/build.dart @@ -0,0 +1,11 @@ +import 'package:hooks/hooks.dart'; +import 'package:native_toolchain_rs/native_toolchain_rs.dart'; + +void main(List args) async { + await build(args, (input, output) async { + await const RustBuilder(assetName: 'src/ffi.g.dart').run( + input: input, + output: output, + ); + }); +} diff --git a/examples/dart_only/lib/dart_only_example.dart b/examples/dart_only/lib/dart_only_example.dart new file mode 100644 index 0000000..903aefe --- /dev/null +++ b/examples/dart_only/lib/dart_only_example.dart @@ -0,0 +1 @@ +export 'package:dart_only_example/src/ffi.g.dart' show rust_add; diff --git a/examples/dart_only/lib/src/ffi.g.dart b/examples/dart_only/lib/src/ffi.g.dart new file mode 100644 index 0000000..32af191 --- /dev/null +++ b/examples/dart_only/lib/src/ffi.g.dart @@ -0,0 +1,11 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native() +external int rust_add( + int a, + int b, +); diff --git a/examples/dart_only/pubspec.yaml b/examples/dart_only/pubspec.yaml new file mode 100644 index 0000000..a45130e --- /dev/null +++ b/examples/dart_only/pubspec.yaml @@ -0,0 +1,20 @@ +name: dart_only_example +publish_to: none +resolution: workspace + +environment: + sdk: ^3.9.0 + +dependencies: + hooks: ^0.20.1 + native_toolchain_rs: ^0.1.1 + +dev_dependencies: + # TODO use released version + # ffigen: ^20.0.0-dev.0 + ffigen: + git: + url: https://github.com/dart-lang/native + path: pkgs/ffigen + ref: 85fa542 + test: ^1.26.3 diff --git a/examples/dart_only/rust/Cargo.toml b/examples/dart_only/rust/Cargo.toml new file mode 100644 index 0000000..13dde5d --- /dev/null +++ b/examples/dart_only/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "dart-only-example" +edition = "2024" + +[lib] +crate-type = ["staticlib", "cdylib"] + +[build-dependencies] +cc = "1.2.39" +cbindgen = "0.29.0" diff --git a/examples/dart_only/rust/bindings.h b/examples/dart_only/rust/bindings.h new file mode 100644 index 0000000..bff429b --- /dev/null +++ b/examples/dart_only/rust/bindings.h @@ -0,0 +1,10 @@ +/* +This file is automatically generated by build.rs; DO NOT MANUALLY EDIT! +This header is designed to be consumed by ffigen over on the Dart side. +*/ + +#include + +extern int32_t c_add(int32_t a, int32_t b); + +int32_t rust_add(int32_t a, int32_t b); diff --git a/examples/dart_only/rust/build.rs b/examples/dart_only/rust/build.rs new file mode 100644 index 0000000..4267e68 --- /dev/null +++ b/examples/dart_only/rust/build.rs @@ -0,0 +1,32 @@ +use std::env; + +use cbindgen::Language; + +fn main() { + println!("cargo:rerun-if-changed=c/c_add.h"); + println!("cargo:rerun-if-changed=c/c_add.c"); + + cc::Build::new() + .file("c/c_add.c") + .include("c") + .compile("c_add"); + + println!("cargo:rustc-link-lib=static=c_add"); + + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(Language::C) + .with_no_includes() + .with_sys_include("stdint.h") + .with_autogen_warning( + "/* +This file is automatically generated by build.rs; DO NOT MANUALLY EDIT! +This header is designed to be consumed by ffigen over on the Dart side. +*/", + ) + .generate() + .expect("Unable to generate bindings") + .write_to_file("bindings.h"); +} diff --git a/examples/dart_only/rust/c/c_add.c b/examples/dart_only/rust/c/c_add.c new file mode 100644 index 0000000..0ab4161 --- /dev/null +++ b/examples/dart_only/rust/c/c_add.c @@ -0,0 +1,5 @@ +#include "c_add.h" + +int32_t c_add(int32_t a, int32_t b) { + return a + b; +} diff --git a/examples/dart_only/rust/c/c_add.h b/examples/dart_only/rust/c/c_add.h new file mode 100644 index 0000000..b9ebdbb --- /dev/null +++ b/examples/dart_only/rust/c/c_add.h @@ -0,0 +1,8 @@ +#ifndef C_ADD_H +#define C_ADD_H + +#include + +int32_t c_add(int32_t a, int32_t b); + +#endif // C_ADD_H diff --git a/examples/dart_only/rust/rust-toolchain.toml b/examples/dart_only/rust/rust-toolchain.toml new file mode 120000 index 0000000..4e9e648 --- /dev/null +++ b/examples/dart_only/rust/rust-toolchain.toml @@ -0,0 +1 @@ +../../../rust-toolchain.toml \ No newline at end of file diff --git a/examples/dart_only/rust/src/lib.rs b/examples/dart_only/rust/src/lib.rs new file mode 100644 index 0000000..e839644 --- /dev/null +++ b/examples/dart_only/rust/src/lib.rs @@ -0,0 +1,16 @@ +unsafe extern "C" { + fn c_add(a: i32, b: i32) -> i32; +} + +#[unsafe(no_mangle)] +pub extern "C" fn rust_add(a: i32, b: i32) -> i32 { + unsafe { c_add(a, b) } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_rust_add() { + assert_eq!(crate::rust_add(1, 1), 2); + } +} diff --git a/examples/dart_only/test/dart_only_example_test.dart b/examples/dart_only/test/dart_only_example_test.dart new file mode 100644 index 0000000..1fe69ae --- /dev/null +++ b/examples/dart_only/test/dart_only_example_test.dart @@ -0,0 +1,8 @@ +import 'package:dart_only_example/dart_only_example.dart'; +import 'package:test/test.dart'; + +void main() { + test('rust_add correctly adds', () { + expect(rust_add(1, 1), equals(2)); + }); +} diff --git a/examples/dart_only/tool/ffigen.dart b/examples/dart_only/tool/ffigen.dart new file mode 100644 index 0000000..8ed1162 --- /dev/null +++ b/examples/dart_only/tool/ffigen.dart @@ -0,0 +1,12 @@ +import 'dart:io'; + +import 'package:ffigen/ffigen.dart'; + +void main() { + final packageRoot = Platform.script.resolve('../'); + FfiGenerator( + headers: Headers(entryPoints: [packageRoot.resolve('rust/bindings.h')]), + output: Output(dartFile: packageRoot.resolve('lib/src/ffi.g.dart')), + functions: Functions.includeSet({'rust_add'}), + ).generate(logger: null); +} diff --git a/examples/flutter/hook/build.dart b/examples/flutter/hook/build.dart new file mode 100644 index 0000000..138390c --- /dev/null +++ b/examples/flutter/hook/build.dart @@ -0,0 +1,11 @@ +import 'package:hooks/hooks.dart'; +import 'package:native_toolchain_rs/native_toolchain_rs.dart'; + +void main(List args) async { + await build(args, (input, output) async { + await const RustBuilder(assetName: 'src/ffi.g.dart').run( + input: input, + output: output, + ); + }); +} diff --git a/examples/flutter/integration_test/widget_test.dart b/examples/flutter/integration_test/widget_test.dart new file mode 100644 index 0000000..6e9b0c3 --- /dev/null +++ b/examples/flutter/integration_test/widget_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_example/main.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter can increment and reset', (tester) async { + await tester.pumpWidget(const MyApp()); + + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.autorenew)); + await tester.pump(); + + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + }); +} diff --git a/examples/flutter/lib/main.dart b/examples/flutter/lib/main.dart new file mode 100644 index 0000000..0f071c6 --- /dev/null +++ b/examples/flutter/lib/main.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_example/src/ffi.g.dart'; + +// NOTE: this is a simple example file +// ignore_for_file: public_member_api_docs + +void main() { + runApp(const MyApp()); +} + +final class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData.dark(), + home: const MyHomePage(title: 'Flutter Example'), + ); + } +} + +final class MyHomePage extends StatefulWidget { + const MyHomePage({required this.title, super.key}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +final class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + get_count().toString(), + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: () => setState(increase_count), + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + const SizedBox(height: 8), + FloatingActionButton( + onPressed: () => setState(reset_count), + tooltip: 'Reset', + child: const Icon(Icons.autorenew), + ), + ], + ), + ); + } +} diff --git a/examples/flutter/lib/src/ffi.g.dart b/examples/flutter/lib/src/ffi.g.dart new file mode 100644 index 0000000..b3ba6c2 --- /dev/null +++ b/examples/flutter/lib/src/ffi.g.dart @@ -0,0 +1,14 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native() +external void reset_count(); + +@ffi.Native() +external void increase_count(); + +@ffi.Native() +external int get_count(); diff --git a/examples/flutter/pubspec.yaml b/examples/flutter/pubspec.yaml new file mode 100644 index 0000000..9bb18e7 --- /dev/null +++ b/examples/flutter/pubspec.yaml @@ -0,0 +1,28 @@ +name: flutter_example +publish_to: none +resolution: workspace + +environment: + sdk: ^3.9.0 + +dependencies: + flutter: + sdk: flutter + hooks: ^0.20.1 + native_toolchain_rs: ^0.1.1 + +dev_dependencies: + # TODO use released version + # ffigen: ^20.0.0-dev.0 + ffigen: + git: + url: https://github.com/dart-lang/native + path: pkgs/ffigen + ref: 85fa542 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/examples/flutter/rust/Cargo.toml b/examples/flutter/rust/Cargo.toml new file mode 100644 index 0000000..473b892 --- /dev/null +++ b/examples/flutter/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "flutter-example" +edition = "2024" + +[lib] +crate-type = ["staticlib", "cdylib"] + +[build-dependencies] +cbindgen = "0.29.0" diff --git a/examples/flutter/rust/bindings.h b/examples/flutter/rust/bindings.h new file mode 100644 index 0000000..7e07d13 --- /dev/null +++ b/examples/flutter/rust/bindings.h @@ -0,0 +1,12 @@ +/* +This file is automatically generated by build.rs; DO NOT MANUALLY EDIT! +This header is designed to be consumed by ffigen over on the Dart side. +*/ + +#include + +void reset_count(void); + +void increase_count(void); + +uint64_t get_count(void); diff --git a/examples/flutter/rust/build.rs b/examples/flutter/rust/build.rs new file mode 100644 index 0000000..dc30029 --- /dev/null +++ b/examples/flutter/rust/build.rs @@ -0,0 +1,22 @@ +use std::env; + +use cbindgen::Language; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(Language::C) + .with_no_includes() + .with_sys_include("stdint.h") + .with_autogen_warning( + "/* +This file is automatically generated by build.rs; DO NOT MANUALLY EDIT! +This header is designed to be consumed by ffigen over on the Dart side. +*/", + ) + .generate() + .expect("Unable to generate bindings") + .write_to_file("bindings.h"); +} diff --git a/examples/flutter/rust/rust-toolchain.toml b/examples/flutter/rust/rust-toolchain.toml new file mode 120000 index 0000000..4e9e648 --- /dev/null +++ b/examples/flutter/rust/rust-toolchain.toml @@ -0,0 +1 @@ +../../../rust-toolchain.toml \ No newline at end of file diff --git a/examples/flutter/rust/src/lib.rs b/examples/flutter/rust/src/lib.rs new file mode 100644 index 0000000..20038ac --- /dev/null +++ b/examples/flutter/rust/src/lib.rs @@ -0,0 +1,37 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +static COUNT: AtomicU64 = AtomicU64::new(0); + +#[unsafe(no_mangle)] +pub extern "C" fn reset_count() { + COUNT.store(0, Ordering::SeqCst); +} + +#[unsafe(no_mangle)] +pub extern "C" fn increase_count() { + COUNT.fetch_add(1, Ordering::SeqCst); +} + +#[unsafe(no_mangle)] +pub extern "C" fn get_count() -> u64 { + COUNT.load(Ordering::SeqCst) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_counter() { + crate::reset_count(); + assert_eq!(crate::get_count(), 0); + + crate::increase_count(); + assert_eq!(crate::get_count(), 1); + + crate::increase_count(); + crate::increase_count(); + assert_eq!(crate::get_count(), 3); + + crate::reset_count(); + assert_eq!(crate::get_count(), 0); + } +} diff --git a/examples/flutter/test/widget_test.dart b/examples/flutter/test/widget_test.dart new file mode 100644 index 0000000..6e9b0c3 --- /dev/null +++ b/examples/flutter/test/widget_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_example/main.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter can increment and reset', (tester) async { + await tester.pumpWidget(const MyApp()); + + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.autorenew)); + await tester.pump(); + + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + }); +} diff --git a/examples/flutter/tool/ffigen.dart b/examples/flutter/tool/ffigen.dart new file mode 100644 index 0000000..a0f7052 --- /dev/null +++ b/examples/flutter/tool/ffigen.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:ffigen/ffigen.dart'; + +void main() { + final packageRoot = Platform.script.resolve('../'); + FfiGenerator( + headers: Headers(entryPoints: [packageRoot.resolve('rust/bindings.h')]), + output: Output(dartFile: packageRoot.resolve('lib/src/ffi.g.dart')), + functions: Functions.includeSet({ + 'reset_count', + 'increase_count', + 'get_count', + }), + ).generate(logger: null); +} diff --git a/pubspec.lock b/pubspec.lock index 5180ebb..7e0c478 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" charcode: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" code_assets: dependency: transitive description: @@ -137,6 +153,39 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + ffigen: + dependency: transitive + description: + path: "pkgs/ffigen" + ref: "85fa542" + resolved-ref: "85fa542e038fd569c3418971bb7034552a98d53a" + url: "https://github.com/dart-lang/native" + source: git + version: "20.0.0-dev.0" file: dependency: transitive description: @@ -145,6 +194,21 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + flutter: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -153,6 +217,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -201,6 +270,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + integration_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" io: dependency: transitive description: @@ -225,6 +299,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" logging: dependency: transitive description: @@ -241,6 +339,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" melos: dependency: "direct dev" description: @@ -369,6 +475,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" shelf: dependency: transitive description: @@ -401,6 +515,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" source_map_stack_trace: dependency: transitive description: @@ -449,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -497,6 +624,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" very_good_analysis: dependency: "direct dev" description: @@ -545,6 +680,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: @@ -571,3 +714,4 @@ packages: version: "2.2.2" sdks: dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 04477e3..0299d4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,8 @@ environment: workspace: - native_toolchain_rs + - examples/dart_only + - examples/flutter dev_dependencies: melos: ^7.1.1 @@ -22,3 +24,21 @@ melos: exec: concurrency: 1 failFast: true + + integration-test: + description: Run integration tests for a specific package in this project. + run: flutter test integration_test + packageFilters: + dirExists: integration_test + exec: + concurrency: 1 + failFast: true + + init-integration-test-boilerplate: + description: Create integration test boilerplate for a specific package in this project. + run: flutter create . + packageFilters: + dirExists: integration_test + exec: + concurrency: 1 + failFast: true diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a815736 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,28 @@ +[toolchain] +channel = "1.90.0" +profile = "default" +components = ["rust-analyzer"] + +targets = [ + # Android + "armv7-linux-androideabi", + "aarch64-linux-android", + "x86_64-linux-android", + + # iOS (device + simulator) + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "x86_64-apple-ios", + + # Windows + "aarch64-pc-windows-msvc", + "x86_64-pc-windows-msvc", + + # Linux + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + + # macOS + "aarch64-apple-darwin", + "x86_64-apple-darwin", +]