diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000..044cd341 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,17 @@ +## Report + +_Please mention the problem here. If it's not a problem, write it as a discussion._ + +## Steps to Reproduce + +_Please describe the exact steps to reproduce the error._ + +## System Information + +_Please provide the output from the command below, using markdown codeblock syntax._ + +```bash +rustc --version +protoc --version +flutter doctor +``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..de3f9737 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Changes + +_Please mention issues fixed by this PR here, using format "Fixes #(Issue number)"._ + +## Before Committing + +_Please make sure that you've formatted the files._ + +``` +dart format . +cargo fmt +cargo clippy --fix --allow-dirty +``` diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml new file mode 100644 index 00000000..a3a4ca95 --- /dev/null +++ b/.github/workflows/build_test.yaml @@ -0,0 +1,131 @@ +name: build-test + +on: + push: + branches: + - main + paths-ignore: + - "**.md" + pull_request: + paths-ignore: + - "**.md" + workflow_dispatch: + +concurrency: + # Cancels the workflow + # when another event in the same context happens. + # If it's a PR, context is the pull request number. + # Otherwise, it uses the Git reference(branch or tag name). + group: > + ${{ github.workflow }} + ${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: ${{ matrix.runner }} / ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false # Important + matrix: + runner: [ubuntu-latest, windows-latest, macos-latest] + target: [android, web] # On all platforms + include: + # Specify targets for each platform + - runner: ubuntu-latest + target: linux + - runner: windows-latest + target: windows + - runner: macos-latest + target: macos + - runner: macos-latest + target: ios + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: true + + - name: Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Setup Protobuf compiler + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install shorthand CLI tool + run: cargo install --path shorthand/ + + - name: Create a Flutter app for testing + run: flutter create test_app + + - name: Setup Ninja and GTK3 toolchain (Only Linux target) + if: matrix.target == 'linux' + run: | + sudo apt-get update -y + sudo apt-get install -y ninja-build libgtk-3-dev + + - name: Setup Java toolchain (Only Android target) + if: matrix.target == 'android' + uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: "11" + + - name: Fetch Flutter dependencies + working-directory: test_app/ + run: flutter pub get + + - name: Add this framework as a dependency + working-directory: test_app/ + run: dart pub add "rust_in_flutter:{'path':'../'}" + + - name: Apply Rust template + working-directory: test_app/ + run: rifs template + + # - name: Generate message files + # working-directory: test_app/ + # run: rifs message + + - name: Build the example app + if: matrix.target == 'linux' + working-directory: test_app/ + run: flutter build linux + + - name: Build the example app + if: matrix.target == 'android' + working-directory: test_app/ + run: | + flutter build apk + flutter build appbundle + + - name: Build the example app + if: matrix.target == 'windows' + working-directory: test_app/ + run: flutter build windows + + - name: Build the example app + if: matrix.target == 'macos' + working-directory: test_app/ + run: flutter build macos + + - name: Build the example app + if: matrix.target == 'ios' + working-directory: test_app/ + run: flutter build ios --no-codesign + + - name: Build the example app + if: matrix.target == 'web' + working-directory: test_app/ + run: | + rifs wasm --release + flutter build web diff --git a/.github/workflows/check_and_lint.yml b/.github/workflows/check_and_lint.yml deleted file mode 100644 index 9c3dab6f..00000000 --- a/.github/workflows/check_and_lint.yml +++ /dev/null @@ -1,26 +0,0 @@ -on: - pull_request: - push: - branches: - - main - -name: Check and Lint - -jobs: - Flutter: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1 - - name: Pub Get - run: dart pub get --no-precompile - working-directory: build_tool - - name: Dart Format - run: dart format . --output=none --set-exit-if-changed - working-directory: build_tool - - name: Analyze - run: dart analyze - working-directory: build_tool - - name: Test - run: dart test - working-directory: build_tool diff --git a/.github/workflows/formatting_test.yaml b/.github/workflows/formatting_test.yaml new file mode 100644 index 00000000..4609f600 --- /dev/null +++ b/.github/workflows/formatting_test.yaml @@ -0,0 +1,82 @@ +name: formatting-test + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + # Cancels the workflow + # when another event in the same context happens. + # If it's a PR, context is the pull request number. + # Otherwise, it uses the Git reference(branch or tag name). + group: > + ${{ github.workflow }} + ${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + dart: + name: dart-and-rust-code + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Setup Protobuf compiler + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install shorthand CLI tool + working-directory: shorthand/ + run: cargo install --path ./ + + - name: Install Clippy + run: rustup component add clippy + + - name: Fetch Flutter dependencies + working-directory: example/ + run: flutter pub get + + - name: Generate message files + working-directory: example/ + run: rifs message + + - name: Format Dart code + run: dart format . + + - name: Format Rust code + run: | + cargo fmt + cargo clippy --fix --allow-dirty + + - name: Check if the code has changed + run: git diff --exit-code + + config: + name: configuration-files + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Check files with Prettier + uses: creyD/prettier_action@v4.3 + with: + dry: True + prettier_options: > + --write + ./**/*.{md,yaml} diff --git a/.github/workflows/test_example_plugin_build.yml b/.github/workflows/test_example_plugin_build.yml deleted file mode 100644 index 70c8101c..00000000 --- a/.github/workflows/test_example_plugin_build.yml +++ /dev/null @@ -1,82 +0,0 @@ -on: - pull_request: - push: - branches: - - main - -name: Test Example Plugin - -jobs: - Build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macOS-latest - - windows-latest - build_mode: - - debug - - profile - - release - env: - EXAMPLE_DIR: "a b/hello_rust_ffi_plugin/example" - CARGOKIT_VERBOSE: 1 - steps: - - name: Extract branch name - shell: bash - run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT - id: extract_branch - - name: Setup Repository - shell: bash - run: | - mkdir "a b" # Space is intentional - cd "a b" - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - # "advanced" branch has extra iOS flavor and uses rust nightly for release builds - git clone -b advanced https://github.com/irondash/hello_rust_ffi_plugin - cd hello_rust_ffi_plugin - git subtree pull --prefix cargokit https://github.com/irondash/cargokit.git ${{ steps.extract_branch.outputs.branch }} --squash - - uses: subosito/flutter-action@v1 - with: - channel: "stable" - - name: Install GTK - if: (matrix.os == 'ubuntu-latest') - run: sudo apt-get update && sudo apt-get install libgtk-3-dev - - name: Install ninja-build - if: (matrix.os == 'ubuntu-latest') - run: sudo apt-get update && sudo apt-get install ninja-build - - name: Build Linux (${{ matrix.build_mode }}) - if: matrix.os == 'ubuntu-latest' - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: flutter build linux --${{ matrix.build_mode }} -v - - name: Build macOS (${{ matrix.build_mode }}) - if: matrix.os == 'macos-latest' - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: flutter build macos --${{ matrix.build_mode }} -v - - name: Build iOS (${{ matrix.build_mode }}) - if: matrix.os == 'macos-latest' - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: flutter build ios --${{ matrix.build_mode }} --no-codesign -v - - name: Build iOS (${{ matrix.build_mode }}) - flavor1 - if: matrix.os == 'macos-latest' - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: flutter build ios --flavor flavor1 --${{ matrix.build_mode }} --no-codesign -v - - name: Build Windows (${{ matrix.build_mode }}) - if: matrix.os == 'windows-latest' - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: flutter build windows --${{ matrix.build_mode }} -v - - name: Build Android (${{ matrix.build_mode }}) - shell: bash - working-directory: ${{ env.EXAMPLE_DIR }} - run: | - export JAVA_HOME=$JAVA_HOME_11_X64 - flutter build apk --${{ matrix.build_mode }} -v - diff --git a/.gitignore b/.gitignore index cf7bb868..30932480 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,35 @@ -target -.dart_tool +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related *.iml -!pubspec.lock +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ + +# Rust related +.cargo/ +target/ + +# Others +**/*.lock \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 00000000..19ba21e9 --- /dev/null +++ b/.metadata @@ -0,0 +1,42 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 796c8ef79279f9c774545b3771238c3098dbefab + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: android + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: ios + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: linux + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: macos + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: windows + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/.pubignore b/.pubignore new file mode 100644 index 00000000..cd2b2b69 --- /dev/null +++ b/.pubignore @@ -0,0 +1,9 @@ +# Remove unnecessary content +# when publishing the package to pub.dev + +/automate/ +/target/ +/shorthand/ +/Cargo.toml + +*.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4f4ca1bb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,464 @@ +## 4.4.0 + +- Improved various guides and comments. +- Fixed a bug that made the app crash when passing in an empty `Vec`. +- Fixed the formatting of Rust files. + +## 4.3.0 + +- Now `flutter run` will use `require-corp` value for `cross-origin-embedder-policy` HTTP header that works on all web browsers. + +## 4.2.1 + +- Fixed a bug with `RustResponse::default()`. + +## 4.2.0 + +- New command `rifs --help`. Thanks `@bookshiyi`! + +## 4.1.4 + +- Fixed a sentence in the guides. + +## 4.1.3 + +- Made `rifs message` command read `PUB_CACHE` enviornment variable if present. Thanks `@rabbitson87`! + +## 4.1.2 + +- Fixed `rifs template` command. + +## 4.1.1 + +- Added some guides to the shorthand crate. +- Removed an unneeded dependency from the shorthand crate. + +## 4.1.0 + +- Fixed `sleep()` on the web. +- Added demo link in the guides. + +## 4.0.3 + +- Fixed bugs with `rifs template` on Windows. +- Fixed outdated comments. +- Organized sample code. + +## 4.0.2 + +- Eliminated an unnecessary Dart dependency. + +## 4.0.1 + +- Eliminated an unnecessary Dart dependency. + +## 4.0.0 + +- Added support for sending large binaries between Dart and Rust. This is now possible by using the `blob` field in `RustRequest`, `RustResponse`, and `RustSignal`. Please make sure you've run `rifs template` before using this new version because the template has changed a little. +- Added support for nested message folders. +- Added support for Rust nightly. +- Eliminated unnecessary Dart dependencies. + +## 3.7.4 + +- Updated `cargokit`, the build connector between Flutter and Rust. + +## 3.7.3 + +- Fixed a bug with cargo. + +## 3.7.2 + +- Fixed a bug with cargo. + +## 3.7.1 + +- Organized descriptions and files. + +## 3.7.0 + +- Now this framework provides a shorthand command `rifs ...` which is equivalent to `dart run rust_in_flutter ...`. + +## 3.6.0 + +- Fixed a bug that prevents the app from running on Linux. +- Improved various texts exposed to developers for clarity. + +## 3.5.1 + +- Bumped `prost` version to avoid snake case related warnings. + +## 3.5.0 + +- Shortend some names that were unnecessarily long. + +## 3.4.5 + +- Import statements became shorter in Dart. + +## 3.4.4 + +- Cleaned up outdated dependencies in `Cargo.toml`. + +## 3.4.3 + +- Now `syntax` and `package` statements in `.proto` files should be handled automatically. + +## 3.4.2 + +- Now running `dart run rust_in_flutter message` verifies `package` statement in `.proto` files and mistakes are fixed automatically. + +## 3.4.1 + +- Now match statement is used for handling requests. This improves code readability. + +## 3.4.0 + +- Now each `.proto` file is treated as a Rust resource, which essentially becomes an API endpoint. + +## 3.3.0 + +- `RustResource` enum has been added to `interaction.proto`. Now the list of available Rust resources are managed by Protobuf, which makes the project less error-prone. This new system also has less runtime overhead because interactions are distinguished by simple integers, not strings. + +## 3.2.3 + +- Improved guides. + +## 3.2.2 + +- Organized guides. + +## 3.2.1 + +- Matched first guides with the docs. + +## 3.2.0 + +- Now when applying the Rust template with `dart run rust_in_flutter template`, `README.md` file will get a new section explaining about this framework. + +## 3.1.1 + +- Updated docs link. + +## 3.1.0 + +- Now there's a new Dart command `message`. Developers can now generate Dart and Rust message code from `.proto` files with `dart run rust_in_flutter message`. `build.rs` file that used to do this is removed. + +## 3.0.9 + +- Fixed a problem with pub.dev score. + +## 3.0.8 + +- Fixed a problem with pub.dev score. + +## 3.0.7 + +- Fixed a problem with pub.dev score. + +## 3.0.6 + +- Fixed a problem with pub.dev score. + +## 3.0.5 + +- Moved documentation to a dedicated website. +- Now `build.rs` will automatically modify PATH for `protoc-gen-dart`. +- Fixed an error appearing in Rust-analyzer's webassembly mode. + +## 3.0.4 + +- Polished template code. + +## 3.0.3 + +- Polished template code. + +## 3.0.2 + +- Polished guides, comments and template code. + +## 3.0.1 + +- Fixed and organized tutorials and comments. + +## 3.0.0 + +- Adopted Protobuf for message serialization. Now communication between Dart and Rust is much more type-safe and faster than before. Because the template has now changed, you need to run `dart run rust_in_flutter template` again when migrating from version 2. Thanks `@wheregmis` and `@bookshiyi`! + +## 2.9.0 + +- Removed `corrosion`. Now this package solely relies on `cargokit` and is much more slimmer. Thanks `@bookshiyi`! +- Removed unneeded files from pub.dev publication. + +## 2.8.5 + +- Fixed a problem with pub.dev score. + +## 2.8.4 + +- Fixed a problem with pub.dev score. + +## 2.8.3 + +- Wrote new catchphrase. + +## 2.8.2 + +- Updated links. + +## 2.8.1 + +- Updated links. + +## 2.8.0 + +- Removed unneeded dependencies. + +## 2.7.4 + +- Fixed CI badge showing rate limit error. + +## 2.7.3 + +- Fixed wrong guides. + +## 2.7.2 + +- Organized guides. + +## 2.7.1 + +- Organized guides. Thanks `@bookshiyi`! + +## 2.7.0 + +- Stabilized web-related Rust toolchain's auto-installation. Thanks `@bookshiyi`! + +## 2.6.0 + +- Applied continuous integration for checking builds and improving project stability. Thanks `@bookshiyi`! + +## 2.5.6 + +- Updated Cargokit. Thanks `@bookshiyi`! + +## 2.5.5 + +- Improved guides about HTTP headers. + +## 2.5.4 + +- Updated example code. + +## 2.5.3 + +- Improved guides and CLI messages. + +## 2.5.2 + +- Optimized web binary size. + +## 2.5.1 + +- Optimized web performance. + +## 2.5.0 + +- Now Rust logic will be restarted upon Dart's hot restart on the web too. +- CLI commands are shortened. + +## 2.4.0 + +- Fixed the problem with dangling threads from the `tokio` runtime remaining after closing the Flutter app. Even after the app window was closed, `tokio` threads were still running, resulting in becoming a background process without a window. Now the `tokio` runtime will properly be shut down. + +## 2.3.2 + +- Re-publishing due to `pub.dev`'s `[UNKNOWN PLATFORMS]` error. + +## 2.3.1 + +- Restored the benefits section in the first guide. + +## 2.3.0 + +- Improved Dart's hot restart process on native platforms. + +## 2.2.0 + +- Improved the procedure of building for the web. +- Simplfied unneeded complexities. + +## 2.1.2 + +- Improved web alias module. +- Fixed small things. + +## 2.1.1 + +- Optimized the bridge thread on native platforms. +- Updated many minor errors in the guides. +- Fixed a problem with import statement not being written in `./lib/main.dart` when applying Rust template. + +## 2.1.0 + +- Merged `frb_engine` crate into `hub`. +- Removed unneeded intermediate worker pools. +- Added `time` web alias import. +- Added many guides and comments. + +## 2.0.1 + +- Improved guides. +- Added `print!` web alias macro. +- Organized exposed Dart APIs. + +## 2.0.0 + +- Added web support. + +## 1.6.6 + +- Improved guides. +- Now, the template application command will check if the current directory is a Flutter project first. + +## 1.6.5 + +- Improved guides. + +## 1.6.4 + +- Organized guide sections. + +## 1.6.3 + +- Organized guide sections. + +## 1.6.2 + +- Filled in missing translations. + +## 1.6.1 + +- Slightly improved guide sections. + +## 1.6.0 + +- Added step-by-step guides. + +## 1.5.3 + +- Fixed some example app code. + +## 1.5.2 + +- Improved the readability of example app code. + +## 1.5.1 + +- Added Japanese translation. +- Fixed some sentences in Korean guides. + +## 1.5.0 + +- Now the Android NDK version that the Flutter SDK expects will be used, not the version specified by this package. +- Fixed a bug saying `IntoDart` trait is not implemented. + +## 1.4.1 + +- Improved various guides. + +## 1.4.0 + +- Filled in various guides to help developers understand the structure more easily. + +## 1.3.2 + +- Added Chinese guides. Thanks `@moluopro`! +- Added Korean guides. +- Added guides about build tool version issues. +- Added guides about library bundling. + +## 1.3.1 + +- Fixed a problem with Rust crate path detection on Android. + +## 1.3.0 + +- Changed the name of an exposed enum. Now `Operation` has changed to `RustOperation` so that it won't make confusions with other operations. All developers should update their code to match this new name, probably using the batch replace function in various IDEs. +- Updated code snippets. + +## 1.2.8 + +- Fixed small things. + +## 1.2.7 + +- Stabilized `main.dart` modifcation upon `dart run rust_in_flutter:apply_template`. + +## 1.2.6 + +- Hid the information regarding the compilation of connector crates to avoid confusion with actual crates. + +## 1.2.5 + +- Updated the guide about Android NDK version. + +## 1.2.4 + +- Updated many outdated comments and guides. +- Decreased the time spent on `ensureInitialized`. Also, `ensureInitialized()` is automatically inserted in `main.dart` when doing `dart run rust_in_flutter:apply_template` from now on. +- Various code improvements were applied. + +## 1.2.3 + +- Clarified template structure in guides. + +## 1.2.2 + +- Hide more Dart APIs that are not meant to be used outside. + +## 1.2.1 + +- Updated many comments. +- Fine-tuned the visibility of Dart APIs. +- Organized guides. + +## 1.2.0 + +- Made the Rust request handler more future-proof, taking potential web support into account. + +## 1.1.1 + +- Improved various guides to help understanding the features of this package. + +## 1.1.0 + +- Now this package is a Flutter FFI plugin without dummy native code. +- Improved guides + +## 1.0.4 + +- Fixed a problem with library bundling on Linux. +- Added comments. +- Added guides. +- Improved template application. + +## 1.0.3 + +- Included code snippets in guides. + +## 1.0.2 + +- Fixed typos. +- Organized inner code. + +## 1.0.1 + +- Enforced bundling on macOS and iOS. +- Improved pub score. +- Make `apply_rust` modify `.gitignore`. + +## 1.0.0 + +- Previously `flutter_rust_app_template`, now this is a small convenient framework that can be applied to existing Flutter projects. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..b760df34 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["./example/native/*", "./shorthand"] +resolver = "2" diff --git a/LICENSE b/LICENSE index 54a7d589..f69a0cf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,39 +1,21 @@ -Copyright 2022 Matej Knopp +MIT License -================================================================================ - -MIT LICENSE +Copyright (c) 2023 Cunarist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -================================================================================ - -APACHE LICENSE, VERSION 2.0 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..0af17d8c --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Rust-In-Flutter + +[![Pub Version](https://img.shields.io/pub/v/rust_in_flutter)](https://pub.dev/packages/rust_in_flutter) +[![Pub Popularity](https://img.shields.io/pub/popularity/rust_in_flutter)](https://pub.dev/packages/rust_in_flutter) +[![Pub Points](https://img.shields.io/pub/points/rust_in_flutter)](https://pub.dev/packages/rust_in_flutter) +[![GitHub Stars](https://img.shields.io/github/stars/cunarist/rust-in-flutter)](https://github.com/cunarist/rust-in-flutter/stargazers) +[![Build Test](https://github.com/cunarist/rust-in-flutter/actions/workflows/build_test.yaml/badge.svg)](https://github.com/cunarist/rust-in-flutter/actions/workflows/build_test.yaml?query=branch%3Amain) +[![GitHub License](https://img.shields.io/github/license/cunarist/rust-in-flutter)](https://github.com/cunarist/rust-in-flutter/blob/main/LICENSE) + +**"Rust as your Flutter backend, Flutter as your Rust frontend"** + +![preview](https://github.com/cunarist/rust-in-flutter/assets/66480156/be85cf04-2240-497f-8d0d-803c40536d8e) + +Designed for ease of use, future scalability, and unparalleled performance, this lightweight framework takes care of all the complexity behind the scenes. No messing with sensitive build files, no excessive code generation during development. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! + +## 🎮 Demo + +Visit the [demo](https://rif-example.cunarist.com/) running on the web to experience the smoothness and delightfulness that comes from the combination of Flutter and Rust. You can also dive into the [example code](https://github.com/cunarist/rust-in-flutter/tree/main/example). + +## 🖥️ Platform Support + +All platforms available with Flutter are [tested](https://github.com/cunarist/rust-in-flutter/actions/workflows/build_test.yaml?query=branch%3Amain) and supported. Challenging build settings are automatically handled by this framework. + +- ✅ Linux: Tested and supported +- ✅ Android: Tested and supported +- ✅ Windows: Tested and supported +- ✅ macOS: Tested and supported +- ✅ iOS: Tested and supported +- ✅ Web: Tested and supported + +## 🚀 Benefits + +- Rust integration with the ability to use an arbitrary number of library crates +- Async interaction with no blocking +- RESTful API with easy request from Dart and response from Rust +- Streaming from Rust to Dart +- Type-safe and flexible messages powered by Protobuf +- Automatic restart of Rust logic on Dart's hot restart +- No memory copy when sending native data + +## 🐦 Why Use Flutter? + +While Rust is a powerful language for high-performance native programming, its ecosystem for building graphical user interfaces is far from being mature. Though Rust already has some GUI frameworks, they don't compete with extensive support and smooth development experience that Flutter provides. It's only Flutter that compiles to all 6 major platforms from a single codebase. + +Flutter is a powerful and versatile framework that has gained immense popularity for building cross-platform applications with stunning user interfaces. It provides declarative pattern, beautiful widgets, hot reload, convenient debugging tools, and dedicated packages for user interfaces right out-of-the-box. + +## 🦀 Why Use Rust? + +While Dart excels as an amazing object-oriented language for GUI apps, its non-native garbage collection may not always meet demanding performance requirements, and it may lack advanced data manipulation packages. This is where Rust steps in, offering an incredible speed advantage of roughly [2~40 times faster](https://programming-language-benchmarks.vercel.app/dart-vs-rust) than Dart, alongside the ability to leverage multiple threads and various crates that get the job done. + +Rust has garnered a devoted following, being [the most loved programming language](https://survey.stackoverflow.co/2022#section-most-loved-dreaded-and-wanted-programming-scripting-and-markup-languages) on Stack Overflow. Its native performance, thanks to the zero-cost abstraction philosophy, ensures high productivity. Many developers foresee Rust potentially replacing C++ in the future. Rust's simplicity, memory safety, superior performance in various scenarios, vibrant community, and robust tooling support contribute to its growing popularity. + +## 📖 Documentation + +Check out the [documentation](https://docs.cunarist.com/rust-in-flutter) for everything you need to know about how to use this thing. + +## 👥 Contributors + +We appreciate your contribution to the development of this project! + +[![GitHub contributors (via allcontributors.org)](https://contrib.rocks/image?repo=cunarist/rust-in-flutter)](https://github.com/cunarist/rust-in-flutter/graphs/contributors) + +## ☕ Support Us + +If you are benefiting from the features of Rust-In-Flutter and find it helpful, why not consider supporting this project? Your generous donations contribute to the maintenance and development of Rust-In-Flutter, ensuring its continuous improvement and growth. 😉 + +If you feel like so, please consider [buying us a coffee](https://www.buymeacoffee.com/cunarist). diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..af662119 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,85 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.cunarist.rust_in_flutter' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 31 + + // Simply use the `android.ndkVersion` + // declared in the `./android/app/build.gradle` file of the Flutter project. + ndkVersion android.ndkVersion + + // Invoke the shared CMake build with the Android Gradle Plugin. + externalNativeBuild { + cmake { + path "../src/CMakeLists.txt" + + // The default CMake version for the Android Gradle Plugin is 3.10.2. + // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake + // + // The Flutter tooling requires that developers have CMake 3.10 or later + // installed. You should not increase this version, as doing so will cause + // the plugin to fail to compile for some customers of the plugin. + // version "3.10.2" + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + } + + task generateProtobufMessages(type: Exec) { + commandLine 'dart', 'run', 'rust_in_flutter', 'message' + standardOutput = new ByteArrayOutputStream() + errorOutput = new ByteArrayOutputStream() + workingDir = rootProject.projectDir.parent + doFirst { + println "Generating protobuf messages" + } + doLast { + if (execResult.exitValue != 0) { + throw new GradleException("Generation of protobuf messages failed:\n\n${standardOutput.toString()}\n\n${errorOutput.toString()}") + } else { + println "Standard Output:\n${standardOutput.toString()}" + println "Standard Error:\n${errorOutput.toString()}" + } + } + } + preBuild.dependsOn generateProtobufMessages +} + +// Include Rust crates in the build process +apply from: "../cargokit/gradle/plugin.gradle" +cargokit { + manifestDir = "${rootProject.projectDir}/../native/hub" + libname = "hub" +} diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 00000000..10b0dc75 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'rust_in_flutter' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..811c6b5b --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/automate/__main__.py b/automate/__main__.py new file mode 100644 index 00000000..a659134d --- /dev/null +++ b/automate/__main__.py @@ -0,0 +1,131 @@ +import os +import sys +import re + + +def exit(): + print("") + sys.exit() + + +def replace_string_in_files(directory: str, search_string: str, replace_string: str): + for filename in os.listdir(directory): + if not os.path.isdir(os.path.join(directory, filename)): + filepath = os.path.join(directory, filename) + with open(filepath, mode="r", encoding="utf8") as file: + content: str = file.read() + content = content.replace(search_string, replace_string) + with open(filepath, mode="w", encoding="utf8") as file: + file.write(content) + + +def remove_files_in_folder(directory: str, prefix: str): + for filename in os.listdir(directory): + if filename.startswith(prefix): + filepath = os.path.join(directory, filename) + if os.path.isfile(filepath): + os.remove(filepath) + + +print("") + +if len(sys.argv) == 1: + print("Automation option is not provided.") + print("Use `python automate --help` to see all available operations.") + +elif sys.argv[1] == "bridge-gen": + # Temporarily add `ffi` package + # because `flutter_rust_bridge_codegen` wants it, + # though the generated code doesn't use it. + command = "dart pub add ffi" + os.system(command) + + # Delete previous bridge files. + remove_files_in_folder("./example/native/hub/src/bridge", "bridge") + remove_files_in_folder("./lib/src", "bridge") + + # Generate bridge files. + command = "flutter_rust_bridge_codegen" + command += " --rust-input ./example/native/hub/src/bridge/api.rs" + command += " --rust-output ./example/native/hub/src/bridge/bridge_generated.rs" + command += " --dart-output ./lib/src/bridge_generated.dart" + command += " --dart-decl-output ./lib/src/bridge_definitions.dart" + command += " --class-name Bridge" + command += " --wasm" + os.system(command) + + # Remove an unnecessary root import. + filepath = "./example/native/hub/src/lib.rs" + with open(filepath, mode="r", encoding="utf8") as file: + lines = file.readlines() + for turn, line in enumerate(lines): + if "AUTO INJECTED BY flutter_rust_bridge" in line: + lines[turn] = "" + with open(filepath, mode="w", encoding="utf8") as file: + file.write("".join(lines)) + + # Modify some code. + directory_path = "./lib/src/" + search_string = "package:flutter_rust_bridge/flutter_rust_bridge.dart" + replace_string = "bridge_engine/exports.dart" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "\nimport 'package:uuid/uuid.dart';" + replace_string = "" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "generated by flutter_rust_bridge" + replace_string = "generated by flutter_rust_bridge_codegen" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "import 'package:meta/meta.dart';" + replace_string = "" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "@protected" + replace_string = "" + replace_string_in_files(directory_path, search_string, replace_string) + + directory_path = "./example/native/hub/src/bridge" + search_string = "flutter_rust_bridge::" + replace_string = "crate::bridge::bridge_engine::" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "crate::bridge::api_web::" + replace_string = "crate::bridge::api::" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "FLUTTER_RUST_BRIDGE_HANDLER" + replace_string = "BRIDGE_HANDLER" + replace_string_in_files(directory_path, search_string, replace_string) + search_string = "Generated by `flutter_rust_bridge`" + replace_string = "Generated by flutter_rust_bridge_codegen" + replace_string_in_files(directory_path, search_string, replace_string) + + # Format code. + command = "dart format ." + os.system(command) + command = "cargo fmt" + os.system(command) + command = "cargo clippy --fix --allow-dirty" + os.system(command) + + # Remove temporarily added `ffi` package. + command = "dart pub remove ffi" + os.system(command) + +elif sys.argv[1] == "cargokit-update": + print("Updating CargoKit...") + command = "git subtree pull" + command += " --prefix cargokit" + command += " https://github.com/irondash/cargokit.git" + command += " main" + command += " --squash" + os.system(command) + +elif sys.argv[1] == "--help" or sys.argv[1] == "-h": + print("Usage: python automate [arguments]") + print("Arguments:") + print(" -h, --help Shows this usage information.") + print(" bridge-gen Generates bridge files.") + print(" cargokit-update Updates CargoKit.") + +else: + print("No such option for automation is available.") + print("Use `python automate --help` to see all available operations.") + +exit() diff --git a/bin/rust_in_flutter.dart b/bin/rust_in_flutter.dart new file mode 100644 index 00000000..b948ca0b --- /dev/null +++ b/bin/rust_in_flutter.dart @@ -0,0 +1,560 @@ +import 'dart:io'; +import 'package:package_config/package_config.dart'; +import 'dart:convert'; + +Future main(List args) async { + if (args.length == 0) { + print("No operation is provided."); + print("Use `rifs --help` to see all available operations."); + return; + } + switch (args[0]) { + case "template": + await _applyRustTemplate(); + break; + case "message": + await _generateMessageCode(); + break; + case "wasm": + if (args.contains("--release") || args.contains("-r")) { + await _buildWebassembly(isReleaseMode: true); + } else { + await _buildWebassembly(isReleaseMode: false); + } + break; + case "--help": + case "-h": + default: + print("Usage: rifs [arguments]"); + print("Arguments:"); + print(" -h, --help Shows this usage information."); + print(" template Applies Rust template to the current project."); + print(" message Generates message code from `.proto` files."); + print(" wasm Builds webassembly."); + } +} + +/// Creates new folders and files to an existing Flutter project folder. +Future _applyRustTemplate() async { + // Get the path of the current project directory + final flutterProjectPath = Directory.current.path; + + // Get the package directory path + final packageConfig = await findPackageConfig(Directory.current); + if (packageConfig == null) { + return; + } + final packageName = 'rust_in_flutter'; + final package = packageConfig.packages.firstWhere( + (p) => p.name == packageName, + ); + final packagePath = package.root.toFilePath(); + + // Check if current folder is a Flutter project. + final mainFile = File('$flutterProjectPath/lib/main.dart'); + final isFlutterProject = await mainFile.exists(); + if (!isFlutterProject) { + print("\nThis folder doesn't look like a Flutter project. Aborting...\n"); + return; + } + + // Copy basic folders needed for Rust to work + final templateSource = Directory('$packagePath/example/native'); + final templateDestination = Directory('$flutterProjectPath/native'); + await _copyDirectory(templateSource, templateDestination); + final messagesSource = Directory('$packagePath/example/messages'); + final messagesDestination = Directory('$flutterProjectPath/messages'); + await _copyDirectory(messagesSource, messagesDestination); + + // Copy `Cargo.toml` + final cargoSource = File('$packagePath/example/Cargo.toml'); + final cargoDestination = File('$flutterProjectPath/Cargo.toml'); + await cargoSource.copy(cargoDestination.path); + + // Add some lines to `.gitignore` + final rustSectionTitle = '# Rust related'; + final messageSectionTitle = '# Generated messages'; + final gitignoreFile = File('$flutterProjectPath/.gitignore'); + if (!(await gitignoreFile.exists())) { + await gitignoreFile.create(recursive: true); + } + final gitignoreContent = await gitignoreFile.readAsString(); + var gitignoreSplitted = gitignoreContent.split('\n\n'); + gitignoreSplitted = gitignoreSplitted.map((s) => s.trim()).toList(); + if (!gitignoreContent.contains(rustSectionTitle)) { + var text = rustSectionTitle; + text += '\n' + '.cargo/'; + text += '\n' + 'target/'; + gitignoreSplitted.add(text); + } + if (!gitignoreContent.contains(messageSectionTitle)) { + var text = messageSectionTitle; + text += '\n' + '*/**/messages/'; + gitignoreSplitted.add(text); + } + await gitignoreFile.writeAsString(gitignoreSplitted.join('\n\n') + '\n'); + + // Add some guides to `README.md` + final guideSectionTitle = '## Using Rust Inside Flutter'; + final readmeFile = File('$flutterProjectPath/README.md'); + if (!(await readmeFile.exists())) { + await readmeFile.create(recursive: true); + } + final readmeContent = await readmeFile.readAsString(); + var readmeSplitted = readmeContent.split('\n\n'); + readmeSplitted = readmeSplitted.map((s) => s.trim()).toList(); + if (!readmeContent.contains(guideSectionTitle)) { + final text = ''' +$guideSectionTitle + +This project leverages Flutter for GUI and Rust for the backend logic, +utilizing the capabilities of the +[Rust-In-Flutter](https://pub.dev/packages/rust_in_flutter) framework. + +To run and build this app, you need to have +[Flutter SDK](https://docs.flutter.dev/get-started/install), +[Rust toolchain](https://www.rust-lang.org/tools/install), +and [Protobuf compiler](https://grpc.io/docs/protoc-installation) +installed on your system. +You can check that your system is ready with the commands below. +Note that all the Flutter subcomponents should be installed. + +```bash +rustc --version +protoc --version +flutter doctor +``` + +You also need to have the CLI tool for Rust-In-Flutter ready. + +```bash +cargo install rifs +``` + +Messages sent between Dart and Rust are implemented using Protobuf. +If you have newly cloned the project repository +or made changes to the `.proto` files in the `./messages` directory, +run the following command: + +```bash +rifs message +``` + +For detailed instructions on writing Rust and Flutter together, +please refer to Rust-In-Flutter's [documentation](https://docs.cunarist.com/rust-in-flutter). +'''; + readmeSplitted.add(text); + } + await readmeFile.writeAsString(readmeSplitted.join('\n\n') + '\n'); + + // Add Dart dependencies + await Process.run('dart', ['pub', 'add', 'protobuf']); + + // Modify `./lib/main.dart` + await Process.run('dart', ['format', './lib/main.dart']); + var mainText = await mainFile.readAsString(); + if (!mainText.contains('package:rust_in_flutter/rust_in_flutter.dart')) { + final lines = mainText.split("\n"); + final lastImportIndex = lines.lastIndexWhere( + (line) => line.startsWith('import '), + ); + lines.insert( + lastImportIndex + 1, + "import 'package:rust_in_flutter/rust_in_flutter.dart';", + ); + mainText = lines.join("\n"); + } + if (mainText.contains('main() {')) { + mainText = mainText.replaceFirst( + 'main() {', + 'main() async {', + ); + } + if (!mainText.contains('RustInFlutter.ensureInitialized()')) { + mainText = mainText.replaceFirst( + 'main() async {', + 'main() async { await RustInFlutter.ensureInitialized();', + ); + } + await mainFile.writeAsString(mainText); + await Process.run('dart', ['format', './lib/main.dart']); + + print("🎉 Rust template is now ready! 🎉"); +} + +Future _copyDirectory(Directory source, Directory destination) async { + final newDirectory = Directory(destination.path); + await newDirectory.create(); + await for (final entity in source.list(recursive: false)) { + final entityName = entity.path.split(Platform.pathSeparator).last; + if (entity is Directory) { + final newDirectory = Directory( + destination.uri.resolve(entityName).toFilePath(), + ); + await newDirectory.create(); + await _copyDirectory(entity.absolute, newDirectory); + } else if (entity is File) { + await entity.copy( + destination.uri.resolve(entityName).toFilePath(), + ); + } + } +} + +Future _buildWebassembly({bool isReleaseMode = false}) async { + // Verify Rust toolchain. + print("Verifying Rust toolchain for the web." + + " This might take a while if there are new updates to be installed."); + await Process.run("rustup", ["toolchain", "install", "nightly"]); + await Process.run("rustup", [ + "+nightly", + "component", + "add", + "rust-src", + ]); + await Process.run("rustup", [ + "+nightly", + "target", + "add", + "wasm32-unknown-unknown", + ]); // For actual compilation + await Process.run("rustup", [ + "target", + "add", + "wasm32-unknown-unknown", + ]); // For Rust-analyzer + await Process.run("cargo", ["install", "wasm-pack"]); + await Process.run("cargo", ["install", "wasm-bindgen-cli"]); + + // Verify Flutter SDK web server's response headers. + await _verifyServerHeaders(); + + // Prepare the webassembly output path. + final flutterProjectPath = Directory.current; + final wasmOutputPath = flutterProjectPath.uri.resolve('web/pkg').toFilePath(); + + // Build the webassembly module. + print("Compiling Rust..."); + await _compile( + crateDir: './native/hub', + wasmOutput: wasmOutputPath, + isReleaseMode: isReleaseMode, + ); + + print("🎉 Webassembly module is now ready! 🎉"); +} + +Future _verifyServerHeaders() async { + // Get the Flutter SDK's path. + String flutterPath; + if (Platform.isWindows) { + // Windows + final whereFlutterResult = await Process.run('where', ['flutter']); + flutterPath = (whereFlutterResult.stdout as String).split('\n').first; + } else { + // macOS and Linux + final whichFlutterResult = await Process.run('which', ['flutter']); + flutterPath = whichFlutterResult.stdout as String; + } + flutterPath = flutterPath.trim(); + flutterPath = await File(flutterPath).resolveSymbolicLinks(); + flutterPath = File(flutterPath).parent.parent.path; + + // Get the server module file's path. + final serverFile = File( + '$flutterPath/packages/flutter_tools/lib/src/isolated/devfs_web.dart'); + var serverFileContent = await serverFile.readAsString(); + + // Check if the server already includes cross-origin HTTP headers. + if (serverFileContent.contains('cross-origin-opener-policy')) { + return; + } + + // Add the HTTP header code to the server file. + final lines = serverFileContent.split('\n'); + final serverDeclaredIndex = lines.lastIndexWhere( + (line) => line.contains('httpServer = await'), + ); + lines.insert(serverDeclaredIndex + 1, """ +httpServer.defaultResponseHeaders.add( + 'cross-origin-opener-policy', + 'same-origin', +); +httpServer.defaultResponseHeaders.add( + 'cross-origin-embedder-policy', + 'require-corp', +);"""); + serverFileContent = lines.join("\n"); + await serverFile.writeAsString(serverFileContent); + + // Remove the stamp file to make it re-generated. + final flutterToolsStampPath = '$flutterPath/bin/cache/flutter_tools.stamp'; + if (await File(flutterToolsStampPath).exists()) { + await File(flutterToolsStampPath).delete(); + } +} + +Future _compile({ + required String crateDir, + required String wasmOutput, + required bool isReleaseMode, +}) async { + final String crateName = 'hub'; + await _runAdvancedCommand( + 'wasm-pack', + [ + '--quiet', + 'build', '-t', 'no-modules', '-d', wasmOutput, '--no-typescript', + '--out-name', crateName, + if (!isReleaseMode) '--dev', crateDir, + '--', // cargo build args + '-Z', 'build-std=std,panic_abort', + ], + env: { + 'RUSTUP_TOOLCHAIN': 'nightly', + 'RUSTFLAGS': '-C target-feature=+atomics,+bulk-memory,+mutable-globals', + if (stdout.supportsAnsiEscapes) 'CARGO_TERM_COLOR': 'always', + }, + ); +} + +Future _runAdvancedCommand( + String command, + List arguments, { + Map? env, + bool silent = false, +}) async { + final process = await Process.start( + command, + arguments, + environment: env, + ); + final processOutput = []; + process.stderr.transform(utf8.decoder).listen((line) { + if (!silent) stderr.write(line); + processOutput.add(line); + }); + final exitCode = await process.exitCode; + if (exitCode != 0) { + throw ProcessException( + command, arguments, processOutput.join(''), exitCode); + } +} + +Future _generateMessageCode() async { + // Prepare paths. + final flutterProjectPath = Directory.current; + final protoPath = flutterProjectPath.uri.resolve('messages').toFilePath(); + final rustOutputPath = + flutterProjectPath.uri.resolve('native/hub/src/messages').toFilePath(); + final dartOutputPath = + flutterProjectPath.uri.resolve('lib/messages').toFilePath(); + await Directory(rustOutputPath).create(recursive: true); + await _emptyDirectory(rustOutputPath); + await Directory(dartOutputPath).create(recursive: true); + await _emptyDirectory(dartOutputPath); + + // Get the list of `.proto` files. + final resourcesInFolders = >{}; + await _collectProtoFiles( + Directory(protoPath), + Directory(protoPath), + resourcesInFolders, + ); + + // Verify `package` statement in `.proto` files. + // Package name should be the same as the filename + // because Rust filenames are written with package name + // and Dart filenames are written with the `.proto` filename. + for (final entry in resourcesInFolders.entries) { + final subPath = entry.key; + final resourceNames = entry.value; + for (final resourceName in resourceNames) { + final protoFile = File('$protoPath$subPath/$resourceName.proto'); + final lines = await protoFile.readAsLines(); + List outputLines = []; + for (var line in lines) { + final packagePattern = r'^package\s+[a-zA-Z_][a-zA-Z0-9_]*\s*[^=];$'; + if (RegExp(packagePattern).hasMatch(line.trim())) { + continue; + } else if (line.trim().startsWith("syntax")) { + continue; + } else { + outputLines.add(line); + } + } + outputLines.insert(0, 'package $resourceName;'); + outputLines.insert(0, 'syntax = "proto3";'); + await protoFile.writeAsString(outputLines.join('\n') + '\n'); + } + } + + // Generate Rust message files. + print("Verifying `protoc-gen-prost` for Rust." + + " This might take a while if there are new updates to be installed."); + final cargoInstallCommand = await Process.run('cargo', [ + 'install', + 'protoc-gen-prost', + ]); + if (cargoInstallCommand.exitCode != 0) { + throw Exception('Cannot globally install `protoc-gen-prost` Rust crate'); + } + for (final entry in resourcesInFolders.entries) { + final subPath = entry.key; + final resourceNames = entry.value; + Directory('$rustOutputPath$subPath').create(recursive: true); + final protocRustResult = await Process.run('protoc', [ + '--proto_path=$protoPath$subPath', + '--prost_out=$rustOutputPath$subPath', + ...resourceNames.map((name) => '$name.proto'), + ]); + if (protocRustResult.exitCode != 0) { + throw Exception('Could not compile `.proto` files into Rust'); + } + } + + // Generate `mod.rs` for `messages` module in Rust. + for (final entry in resourcesInFolders.entries) { + final subPath = entry.key; + final resourceNames = entry.value; + final modRsLines = resourceNames.map((resourceName) { + return 'pub mod $resourceName;'; + }).toList(); + for (final otherSubPath in resourcesInFolders.keys) { + if (otherSubPath != subPath && otherSubPath.contains(subPath)) { + final relation = otherSubPath + .replaceFirst(subPath, "") + .replaceFirst(Platform.pathSeparator, ''); + if (!relation.contains(Platform.pathSeparator)) { + modRsLines.add('pub mod $relation;'); + } + } + } + final modRsContent = modRsLines.join('\n'); + await File('$rustOutputPath$subPath/mod.rs').writeAsString(modRsContent); + } + + // Generate Dart message files. + print("Verifying `protoc_plugin` for Dart." + + " This might take a while if there are new updates to be installed."); + final pubGlobalActivateCommand = await Process.run('dart', [ + 'pub', + 'global', + 'activate', + 'protoc_plugin', + ]); + if (pubGlobalActivateCommand.exitCode != 0) { + throw Exception('Cannot globally install `protoc_plugin` Dart package'); + } + final newEnvironment = Map.from(Platform.environment); + final currentPathVariable = newEnvironment['PATH']; + var pubCacheBinPath = Platform.isWindows + ? '${Platform.environment['LOCALAPPDATA']}\\Pub\\Cache\\bin' + : '${Platform.environment['HOME']}/.pub-cache/bin'; + if (Platform.environment["PUB_CACHE"] != null) { + final binPath = Platform.isWindows ? '\\bin' : '/bin'; + pubCacheBinPath = '${Platform.environment["PUB_CACHE"]}$binPath'; + } + final separator = Platform.isWindows ? ';' : ':'; + final newPathVariable = currentPathVariable != null + ? '$currentPathVariable$separator$pubCacheBinPath' + : pubCacheBinPath; + newEnvironment['PATH'] = newPathVariable; + for (final entry in resourcesInFolders.entries) { + final subPath = entry.key; + final resourceNames = entry.value; + Directory('$dartOutputPath$subPath').create(recursive: true); + final protocDartResult = await Process.run( + 'protoc', + [ + '--proto_path=$protoPath$subPath', + '--dart_out=$dartOutputPath$subPath', + ...resourceNames.map((name) => '$name.proto'), + ], + environment: newEnvironment, + ); + if (protocDartResult.exitCode != 0) { + throw Exception('Could not compile `.proto` files into Dart'); + } + } + + // Assign Rust resource index to each message module. + var resourceIndex = 0; + for (final entry in resourcesInFolders.entries) { + final subPath = entry.key; + final resourceNames = entry.value; + for (final resourceName in resourceNames) { + _appendLineToFile( + '$rustOutputPath$subPath/$resourceName.rs', + 'pub const ID: i32 = $resourceIndex;', + ); + _appendLineToFile( + '$dartOutputPath$subPath/$resourceName.pb.dart', + 'const ID = $resourceIndex;', + ); + resourceIndex += 1; + } + } + + // Notify that it's done + print("🎉 Message code in Dart and Rust is now ready! 🎉"); +} + +Future _emptyDirectory(String directoryPath) async { + final directory = Directory(directoryPath); + + if (await directory.exists()) { + await for (final entity in directory.list()) { + if (entity is File) { + await entity.delete(); + } else if (entity is Directory) { + await entity.delete(recursive: true); + } + } + } +} + +Future _appendLineToFile(String filePath, String textToAppend) async { + // Read the existing content of the file + final file = File(filePath); + if (!(await file.exists())) { + await file.create(recursive: true); + } + String fileContent = await file.readAsString(); + + // Append the new text to the existing content + fileContent += '\n'; + fileContent += textToAppend; + + // Write the updated content back to the file + await file.writeAsString(fileContent); +} + +Future _collectProtoFiles( + Directory rootDirectory, + Directory directory, + Map> resourcesInFolders, +) async { + final resources = []; + await for (final entity in directory.list()) { + if (entity is File) { + final filename = entity.uri.pathSegments.last; + if (filename.endsWith('.proto')) { + final parts = filename.split('.'); + parts.removeLast(); // Remove the extension from the filename. + final fileNameWithoutExtension = parts.join('.'); + resources.add(fileNameWithoutExtension); + } + } else if (entity is Directory) { + await _collectProtoFiles( + rootDirectory, + entity, + resourcesInFolders, + ); // Recursive call for subdirectories + } + } + final folderPath = directory.path.replaceFirst(rootDirectory.path, ''); + if (resources.length > 0) { + resourcesInFolders[folderPath] = resources; + } +} diff --git a/build_tool/pubspec.lock b/build_tool/pubspec.lock deleted file mode 100644 index 343bdd36..00000000 --- a/build_tool/pubspec.lock +++ /dev/null @@ -1,453 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 - url: "https://pub.dev" - source: hosted - version: "64.0.0" - adaptive_number: - dependency: transitive - description: - name: adaptive_number - sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" - url: "https://pub.dev" - source: hosted - version: "6.2.0" - args: - dependency: "direct main" - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - collection: - dependency: "direct main" - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - convert: - dependency: "direct main" - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - coverage: - dependency: transitive - description: - name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" - url: "https://pub.dev" - source: hosted - version: "1.6.3" - crypto: - dependency: "direct main" - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - ed25519_edwards: - dependency: "direct main" - description: - name: ed25519_edwards - sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - github: - dependency: "direct main" - description: - name: github - sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411" - url: "https://pub.dev" - source: hosted - version: "9.17.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - hex: - dependency: "direct main" - description: - name: hex - sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - http: - dependency: "direct main" - description: - name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" - lints: - dependency: "direct dev" - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: "direct main" - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" - url: "https://pub.dev" - source: hosted - version: "0.12.16" - meta: - dependency: transitive - description: - name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - mime: - dependency: transitive - description: - name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e - url: "https://pub.dev" - source: hosted - version: "1.0.4" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: "direct main" - description: - name: path - sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" - url: "https://pub.dev" - source: hosted - version: "1.8.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 - url: "https://pub.dev" - source: hosted - version: "5.4.0" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - shelf: - dependency: transitive - description: - name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 - url: "https://pub.dev" - source: hosted - version: "1.4.1" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e - url: "https://pub.dev" - source: hosted - version: "1.1.2" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" - source_span: - dependency: "direct main" - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test: - dependency: "direct dev" - description: - name: test - sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" - url: "https://pub.dev" - source: hosted - version: "1.24.6" - test_api: - dependency: transitive - description: - name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" - test_core: - dependency: transitive - description: - name: test_core - sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" - url: "https://pub.dev" - source: hosted - version: "0.5.6" - toml: - dependency: "direct main" - description: - name: toml - sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" - url: "https://pub.dev" - source: hosted - version: "0.14.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - version: - dependency: "direct main" - description: - name: version - sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" - url: "https://pub.dev" - source: hosted - version: "11.9.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b - url: "https://pub.dev" - source: hosted - version: "2.4.0" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - yaml: - dependency: "direct main" - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.0.0 <4.0.0" diff --git a/cargokit/LICENSE b/cargokit/LICENSE new file mode 100644 index 00000000..54a7d589 --- /dev/null +++ b/cargokit/LICENSE @@ -0,0 +1,39 @@ +Copyright 2022 Matej Knopp + +================================================================================ + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ + +APACHE LICENSE, VERSION 2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/README b/cargokit/README similarity index 100% rename from README rename to cargokit/README diff --git a/build_pod.sh b/cargokit/build_pod.sh similarity index 97% rename from build_pod.sh rename to cargokit/build_pod.sh index 481071ee..156f6a54 100755 --- a/build_pod.sh +++ b/cargokit/build_pod.sh @@ -23,7 +23,7 @@ export CARGOKIT_DARWIN_ARCHS=$ARCHS export CARGOKIT_CONFIGURATION=$CONFIGURATION # Path to directory containing Cargo.toml. -export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 +export CARGOKIT_MANIFEST_DIR=$1 #Different from upstream # Temporary directory for build artifacts. export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR diff --git a/build_tool/README.md b/cargokit/build_tool/README.md similarity index 100% rename from build_tool/README.md rename to cargokit/build_tool/README.md diff --git a/build_tool/analysis_options.yaml b/cargokit/build_tool/analysis_options.yaml similarity index 99% rename from build_tool/analysis_options.yaml rename to cargokit/build_tool/analysis_options.yaml index a1aad5b3..57fe1441 100644 --- a/build_tool/analysis_options.yaml +++ b/cargokit/build_tool/analysis_options.yaml @@ -19,7 +19,6 @@ linter: rules: - prefer_relative_imports - directives_ordering - # analyzer: # exclude: # - path/to/excluded/files/** diff --git a/build_tool/bin/build_tool.dart b/cargokit/build_tool/bin/build_tool.dart similarity index 100% rename from build_tool/bin/build_tool.dart rename to cargokit/build_tool/bin/build_tool.dart diff --git a/build_tool/lib/build_tool.dart b/cargokit/build_tool/lib/build_tool.dart similarity index 100% rename from build_tool/lib/build_tool.dart rename to cargokit/build_tool/lib/build_tool.dart diff --git a/build_tool/lib/src/android_environment.dart b/cargokit/build_tool/lib/src/android_environment.dart similarity index 100% rename from build_tool/lib/src/android_environment.dart rename to cargokit/build_tool/lib/src/android_environment.dart diff --git a/build_tool/lib/src/artifacts_provider.dart b/cargokit/build_tool/lib/src/artifacts_provider.dart similarity index 100% rename from build_tool/lib/src/artifacts_provider.dart rename to cargokit/build_tool/lib/src/artifacts_provider.dart diff --git a/build_tool/lib/src/build_cmake.dart b/cargokit/build_tool/lib/src/build_cmake.dart similarity index 100% rename from build_tool/lib/src/build_cmake.dart rename to cargokit/build_tool/lib/src/build_cmake.dart diff --git a/build_tool/lib/src/build_gradle.dart b/cargokit/build_tool/lib/src/build_gradle.dart similarity index 100% rename from build_tool/lib/src/build_gradle.dart rename to cargokit/build_tool/lib/src/build_gradle.dart diff --git a/build_tool/lib/src/build_pod.dart b/cargokit/build_tool/lib/src/build_pod.dart similarity index 100% rename from build_tool/lib/src/build_pod.dart rename to cargokit/build_tool/lib/src/build_pod.dart diff --git a/build_tool/lib/src/build_tool.dart b/cargokit/build_tool/lib/src/build_tool.dart similarity index 100% rename from build_tool/lib/src/build_tool.dart rename to cargokit/build_tool/lib/src/build_tool.dart diff --git a/build_tool/lib/src/builder.dart b/cargokit/build_tool/lib/src/builder.dart similarity index 100% rename from build_tool/lib/src/builder.dart rename to cargokit/build_tool/lib/src/builder.dart diff --git a/build_tool/lib/src/cargo.dart b/cargokit/build_tool/lib/src/cargo.dart similarity index 100% rename from build_tool/lib/src/cargo.dart rename to cargokit/build_tool/lib/src/cargo.dart diff --git a/build_tool/lib/src/crate_hash.dart b/cargokit/build_tool/lib/src/crate_hash.dart similarity index 100% rename from build_tool/lib/src/crate_hash.dart rename to cargokit/build_tool/lib/src/crate_hash.dart diff --git a/build_tool/lib/src/environment.dart b/cargokit/build_tool/lib/src/environment.dart similarity index 100% rename from build_tool/lib/src/environment.dart rename to cargokit/build_tool/lib/src/environment.dart diff --git a/build_tool/lib/src/logging.dart b/cargokit/build_tool/lib/src/logging.dart similarity index 100% rename from build_tool/lib/src/logging.dart rename to cargokit/build_tool/lib/src/logging.dart diff --git a/build_tool/lib/src/options.dart b/cargokit/build_tool/lib/src/options.dart similarity index 100% rename from build_tool/lib/src/options.dart rename to cargokit/build_tool/lib/src/options.dart diff --git a/build_tool/lib/src/precompile_binaries.dart b/cargokit/build_tool/lib/src/precompile_binaries.dart similarity index 100% rename from build_tool/lib/src/precompile_binaries.dart rename to cargokit/build_tool/lib/src/precompile_binaries.dart diff --git a/build_tool/lib/src/rustup.dart b/cargokit/build_tool/lib/src/rustup.dart similarity index 100% rename from build_tool/lib/src/rustup.dart rename to cargokit/build_tool/lib/src/rustup.dart diff --git a/build_tool/lib/src/target.dart b/cargokit/build_tool/lib/src/target.dart similarity index 100% rename from build_tool/lib/src/target.dart rename to cargokit/build_tool/lib/src/target.dart diff --git a/build_tool/lib/src/util.dart b/cargokit/build_tool/lib/src/util.dart similarity index 100% rename from build_tool/lib/src/util.dart rename to cargokit/build_tool/lib/src/util.dart diff --git a/build_tool/lib/src/verify_binaries.dart b/cargokit/build_tool/lib/src/verify_binaries.dart similarity index 100% rename from build_tool/lib/src/verify_binaries.dart rename to cargokit/build_tool/lib/src/verify_binaries.dart diff --git a/build_tool/pubspec.yaml b/cargokit/build_tool/pubspec.yaml similarity index 100% rename from build_tool/pubspec.yaml rename to cargokit/build_tool/pubspec.yaml diff --git a/build_tool/test/builder_test.dart b/cargokit/build_tool/test/builder_test.dart similarity index 100% rename from build_tool/test/builder_test.dart rename to cargokit/build_tool/test/builder_test.dart diff --git a/build_tool/test/cargo_test.dart b/cargokit/build_tool/test/cargo_test.dart similarity index 100% rename from build_tool/test/cargo_test.dart rename to cargokit/build_tool/test/cargo_test.dart diff --git a/build_tool/test/options_test.dart b/cargokit/build_tool/test/options_test.dart similarity index 100% rename from build_tool/test/options_test.dart rename to cargokit/build_tool/test/options_test.dart diff --git a/cmake/cargokit.cmake b/cargokit/cmake/cargokit.cmake similarity index 98% rename from cmake/cargokit.cmake rename to cargokit/cmake/cargokit.cmake index a20fe86e..590fcba0 100644 --- a/cmake/cargokit.cmake +++ b/cargokit/cmake/cargokit.cmake @@ -36,7 +36,7 @@ function(apply_cargokit target manifest_dir lib_name any_symbol_name) set(CARGOKIT_ENV "CARGOKIT_CMAKE=${CMAKE_COMMAND}" "CARGOKIT_CONFIGURATION=$" - "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" + "CARGOKIT_MANIFEST_DIR=${manifest_dir}" # Different from upstream "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" diff --git a/cmake/resolve_symlinks.ps1 b/cargokit/cmake/resolve_symlinks.ps1 similarity index 100% rename from cmake/resolve_symlinks.ps1 rename to cargokit/cmake/resolve_symlinks.ps1 diff --git a/docs/architecture.md b/cargokit/docs/architecture.md similarity index 99% rename from docs/architecture.md rename to cargokit/docs/architecture.md index d9bcf4e2..f023b40c 100644 --- a/docs/architecture.md +++ b/cargokit/docs/architecture.md @@ -38,7 +38,7 @@ This is invoked from plugin's podspec `script_phase` through `build_pod.sh`. Bun Cargokit will build binaries for all active architectures from XCode build and lipo them togherer. -When using Cargokit to integrate Rust code with an application (not a plugin) you can also configure the `Cargo.toml` to just build a dynamic library. When Cargokit finds that the crate only built a dylib and no static lib, it will attempt to replace the Cocoapod framework binary with the dylib. In this case the script `:execution_position` must be set to `:after_compile`. This is *not* recommended for plugins and it's quite experimental. +When using Cargokit to integrate Rust code with an application (not a plugin) you can also configure the `Cargo.toml` to just build a dynamic library. When Cargokit finds that the crate only built a dylib and no static lib, it will attempt to replace the Cocoapod framework binary with the dylib. In this case the script `:execution_position` must be set to `:after_compile`. This is _not_ recommended for plugins and it's quite experimental. ### gen-key, precompile-binaries, verify-binaries @@ -57,6 +57,7 @@ Instead the `run_build_tool` script creates a minimal Dart command line package Cargokit can be configured through a `cargokit.yaml` file, which can be used to control the build of the Rust package and is placed into the Rust crate next to `Cargo.toml`. Here is an example `cargokit.yaml` with comments: + ```yaml cargo: debug: # Configuration of cargo execution during debug builds diff --git a/docs/precompiled_binaries.md b/cargokit/docs/precompiled_binaries.md similarity index 99% rename from docs/precompiled_binaries.md rename to cargokit/docs/precompiled_binaries.md index 2026e867..be4ed4d6 100644 --- a/docs/precompiled_binaries.md +++ b/cargokit/docs/precompiled_binaries.md @@ -53,7 +53,7 @@ The action needs two secrets - private key for signing binaries and GitHub token ```yaml on: push: - branches: [ main ] + branches: [main] name: Precompile Binaries @@ -92,4 +92,3 @@ jobs: By default the `built_tool precompile-binaries` commands build and uploads the binaries for all targets buildable from current host. This can be overriden using the `--target ` argument. Android binaries will be built when `--android-sdk-location` and `--android-ndk-version` arguments are provided. - diff --git a/gradle/plugin.gradle b/cargokit/gradle/plugin.gradle similarity index 98% rename from gradle/plugin.gradle rename to cargokit/gradle/plugin.gradle index a40cf56a..5127f606 100644 --- a/gradle/plugin.gradle +++ b/cargokit/gradle/plugin.gradle @@ -52,7 +52,7 @@ abstract class CargoKitBuildTask extends DefaultTask { def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" def path = Paths.get(new File(pluginFile).parent, "..", executableName); - def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) + def manifestDir = project.cargokit.manifestDir // Different from upstream def rootProjectDir = project.rootProject.projectDir diff --git a/run_build_tool.cmd b/cargokit/run_build_tool.cmd similarity index 100% rename from run_build_tool.cmd rename to cargokit/run_build_tool.cmd diff --git a/run_build_tool.sh b/cargokit/run_build_tool.sh similarity index 100% rename from run_build_tool.sh rename to cargokit/run_build_tool.sh diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 00000000..3faab82f --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,51 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Rust related +.cargo/ +target/ + +# Generated messages +*/**/messages/ diff --git a/example/Cargo.toml b/example/Cargo.toml new file mode 100644 index 00000000..cf8e4662 --- /dev/null +++ b/example/Cargo.toml @@ -0,0 +1,8 @@ +# This file is used for telling Rust-related tools +# where various Rust crates are. +# This also unifies `./target` output folder and +# various Rust configurations. + +[workspace] +members = ["./native/*"] +resolver = "2" diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..3fd37eca --- /dev/null +++ b/example/README.md @@ -0,0 +1,41 @@ +# Example App + +Demonstrates how to use the Rust-In-Flutter framework. + +## Using Rust Inside Flutter + +This project leverages Flutter for GUI and Rust for the backend logic, +utilizing the capabilities of the +[Rust-In-Flutter](https://pub.dev/packages/rust_in_flutter) framework. + +To run and build this app, you need to have +[Flutter SDK](https://docs.flutter.dev/get-started/install), +[Rust toolchain](https://www.rust-lang.org/tools/install), +and [Protobuf compiler](https://grpc.io/docs/protoc-installation) +installed on your system. +You can check that your system is ready with the commands below. +Note that all the Flutter subcomponents should be installed. + +```bash +rustc --version +protoc --version +flutter doctor +``` + +You also need to have the CLI tool for Rust-In-Flutter ready. + +```bash +cargo install rifs +``` + +Messages sent between Dart and Rust are implemented using Protobuf. +If you have newly cloned the project repository +or made changes to the `.proto` files in the `./messages` directory, +run the following command: + +```bash +rifs message +``` + +For detailed instructions on writing Rust and Flutter together, +please refer to Rust-In-Flutter's [documentation](https://docs.cunarist.com/rust-in-flutter). diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 00000000..91cb4523 --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,72 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "com.cunarist.example_app" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.cunarist.example_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..4b83a132 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/rust_in_flutter_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/rust_in_flutter_example/MainActivity.kt new file mode 100644 index 00000000..791192ef --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/rust_in_flutter_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.cunarist.example_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 00000000..f7eb7f63 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..3c472b99 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..9625e105 --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 00000000..fdcc671e --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c71c7df4 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,724 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8C68DA3D5D2DA2A461EE49E3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB9F3893971DE4D759C56DB2 /* Pods_Runner.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CFBCE7F54548A4F5F0435715 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A92C0ACDD1D2A8868BCC0128 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2A2AD670A94B03EAED80AEB8 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5C8D941F1EFA2843B662F9E8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8615267B9B9026D0B00FEA85 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F3BC68FB55DFEB68708B06E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + A92C0ACDD1D2A8868BCC0128 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EB9F3893971DE4D759C56DB2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F8BC85AD321315E790789813 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + FE3623F00D382DF09061646F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2877F9C3FDEF6AADC2A1D752 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CFBCE7F54548A4F5F0435715 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8C68DA3D5D2DA2A461EE49E3 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + E0B556A03FDE6BA1804C0E91 /* Pods */, + EBFB7F471EB14631C7C4FBDB /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + E0B556A03FDE6BA1804C0E91 /* Pods */ = { + isa = PBXGroup; + children = ( + FE3623F00D382DF09061646F /* Pods-Runner.debug.xcconfig */, + 8615267B9B9026D0B00FEA85 /* Pods-Runner.release.xcconfig */, + 2A2AD670A94B03EAED80AEB8 /* Pods-Runner.profile.xcconfig */, + 9F3BC68FB55DFEB68708B06E /* Pods-RunnerTests.debug.xcconfig */, + 5C8D941F1EFA2843B662F9E8 /* Pods-RunnerTests.release.xcconfig */, + F8BC85AD321315E790789813 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + EBFB7F471EB14631C7C4FBDB /* Frameworks */ = { + isa = PBXGroup; + children = ( + EB9F3893971DE4D759C56DB2 /* Pods_Runner.framework */, + A92C0ACDD1D2A8868BCC0128 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 60F92B59B985E70C62999645 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 2877F9C3FDEF6AADC2A1D752 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 065EB341AEDFB48DB9643609 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2AFA76743B3F08DC08FD9831 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 065EB341AEDFB48DB9643609 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2AFA76743B3F08DC08FD9831 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 60F92B59B985E70C62999645 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 674TQU6ZMN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9F3BC68FB55DFEB68708B06E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5C8D941F1EFA2843B662F9E8 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8BC85AD321315E790789813 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 674TQU6ZMN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 674TQU6ZMN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..87131a09 --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..b5b843ad --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 00000000..bcee4eff --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Rust In Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 00000000..fa129a0b --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:rust_in_flutter/rust_in_flutter.dart'; +import 'package:example_app/messages/counter_number.pb.dart' as counterNumber; +import 'package:example_app/messages/mandelbrot.pb.dart' as mandelbrot; + +void main() async { + // Wait for initialization to be completed first. + await RustInFlutter.ensureInitialized(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Home(), + ); + } +} + +class Home extends StatelessWidget { + final ValueNotifier _countNotifier = ValueNotifier(0); + + void _incrementCount() async { + final requestMessage = counterNumber.ReadRequest( + letter: "Hello from Dart!", + beforeNumber: _countNotifier.value, + dummyOne: 1, + dummyTwo: counterNumber.SampleSchema( + sampleFieldOne: true, + sampleFieldTwo: false, + ), + dummyThree: [3, 4, 5], + ); + + final rustRequest = RustRequest( + resource: counterNumber.ID, + operation: RustOperation.Read, + // Convert Dart message object into raw bytes. + message: requestMessage.writeToBuffer(), + ); + + // Use `requestToRust` from `rust_in_flutter.dart` + // to send the request to Rust and get the response. + final rustResponse = await requestToRust(rustRequest); + + if (rustResponse.successful) { + // Convert raw bytes into Dart message objects. + final responseMessage = + counterNumber.ReadResponse.fromBuffer(rustResponse.message!); + _countNotifier.value = responseMessage.afterNumber; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // `StreamBuilder` listens to a stream + // and rebuilds the widget accordingly. + StreamBuilder( + // Receive signals from Rust + // with `rustBroadcaster` from `rust_in_flutter.dart`, + // For better performance, filter signals + // by checking the `resource` field with the `where` method. + // This approach allows the builder to rebuild its widget + // only when there are signals + // with the specific address it is interested in. + stream: rustBroadcaster.stream.where((rustSignal) { + return rustSignal.resource == mandelbrot.ID; + }), + builder: (context, snapshot) { + // If the app has just started and widget is built + // without receiving a Rust signal, + // the snapshot's data will be null. + final rustSignal = snapshot.data; + if (rustSignal == null) { + // Return a black container if the received data is null. + return Container( + margin: const EdgeInsets.all(20), + width: 256, + height: 256, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24.0), + color: Colors.black, + ), + ); + } else { + // Return an image container if some data is received. + final imageData = rustSignal.blob!; + return Container( + margin: const EdgeInsets.all(20), + width: 256, + height: 256, + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: FittedBox( + fit: BoxFit.contain, + child: Image.memory( + imageData, + width: 64, + height: 64, + gaplessPlayback: true, + ), + ), + ), + ); + } + }, + ), + CurrentValueText( + countNotifier: _countNotifier, + ), + ], + ), + ), + // This is a button that calls the increment method. + floatingActionButton: FloatingActionButton( + onPressed: _incrementCount, + child: const Icon(Icons.add), + ), + ); + } +} + +class CurrentValueText extends StatefulWidget { + final ValueNotifier countNotifier; + const CurrentValueText({required this.countNotifier}); + @override + _CurrentValueTextState createState() => _CurrentValueTextState(); +} + +class _CurrentValueTextState extends State { + late int _currentCount; + + @override + void initState() { + super.initState(); + _currentCount = widget.countNotifier.value; + widget.countNotifier.addListener(_updateCurrentCount); + } + + void _updateCurrentCount() { + setState(() { + _currentCount = widget.countNotifier.value; + }); + } + + @override + void dispose() { + widget.countNotifier.removeListener(_updateCurrentCount); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_currentCount == 0) { + return const Text("Not calculated yet"); + } else { + return Text( + "Current value is $_currentCount", + ); + } + } +} diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 00000000..ec5ad8c3 --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.cunarist.rust_in_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..54cc5a88 --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + rust_in_flutter +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/linux/main.cc b/example/linux/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc new file mode 100644 index 00000000..de22e5cf --- /dev/null +++ b/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/linux/my_application.h b/example/linux/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/macos/.gitignore b/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 00000000..c795730d --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..464c19cd --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + AA320D4FFAF7197FEEB683F0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBBC858E6307B3EDA4E70BD8 /* Pods_RunnerTests.framework */; }; + D497552E1F8E4C19D401B9DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9F3BAD2CCB4D25E7388BB5B /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 492DFD2CB3D08365FBDAD1C0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 7A05B3D07413D65AA0B5350B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8F1DA92027EC8D48B1DC2A7F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A2E27389CD2684E64C0D4B08 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + C9F3BAD2CCB4D25E7388BB5B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D596E7EABEC17E8FF7BBA0CE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DBBC858E6307B3EDA4E70BD8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F712690F3459C9357EC00BA3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AA320D4FFAF7197FEEB683F0 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D497552E1F8E4C19D401B9DA /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29AFAD0E41D6F2FDB6E1E2DA /* Pods */ = { + isa = PBXGroup; + children = ( + F712690F3459C9357EC00BA3 /* Pods-Runner.debug.xcconfig */, + D596E7EABEC17E8FF7BBA0CE /* Pods-Runner.release.xcconfig */, + A2E27389CD2684E64C0D4B08 /* Pods-Runner.profile.xcconfig */, + 492DFD2CB3D08365FBDAD1C0 /* Pods-RunnerTests.debug.xcconfig */, + 8F1DA92027EC8D48B1DC2A7F /* Pods-RunnerTests.release.xcconfig */, + 7A05B3D07413D65AA0B5350B /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 29AFAD0E41D6F2FDB6E1E2DA /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C9F3BAD2CCB4D25E7388BB5B /* Pods_Runner.framework */, + DBBC858E6307B3EDA4E70BD8 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + DB7173F051C1D589D3E33CCB /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + D4634949C109D7F638833508 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + D7BE52CD5678A2B48DA54966 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + D4634949C109D7F638833508 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D7BE52CD5678A2B48DA54966 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + DB7173F051C1D589D3E33CCB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 492DFD2CB3D08365FBDAD1C0 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8F1DA92027EC8D48B1DC2A7F /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7A05B3D07413D65AA0B5350B /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e93dc924 --- /dev/null +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..d53ef643 --- /dev/null +++ b/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..e04984ce --- /dev/null +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.cunarist.rustInFlutterExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.cunarist. All rights reserved. diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..5418c9f5 --- /dev/null +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/messages/counter_number.proto b/example/messages/counter_number.proto new file mode 100644 index 00000000..70509294 --- /dev/null +++ b/example/messages/counter_number.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package counter_number; + +message ReadRequest { + string letter = 1; + int32 before_number = 2; + uint32 dummy_one = 3; + SampleSchema dummy_two = 4; + repeated int32 dummy_three = 5; +} + +message ReadResponse { + int32 after_number = 1; + uint32 dummy_one = 2; + SampleSchema dummy_two = 3; + repeated int32 dummy_three = 4; +} + +message SampleSchema { + bool sample_field_one = 1; + bool sample_field_two = 2; +} diff --git a/example/messages/mandelbrot.proto b/example/messages/mandelbrot.proto new file mode 100644 index 00000000..5a962087 --- /dev/null +++ b/example/messages/mandelbrot.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package mandelbrot; + +message StateSignal { + int32 id = 1; + double current_scale = 2; +} diff --git a/example/messages/sample_folder/deeper_folder/deeper_resource.proto b/example/messages/sample_folder/deeper_folder/deeper_resource.proto new file mode 100644 index 00000000..de6bec62 --- /dev/null +++ b/example/messages/sample_folder/deeper_folder/deeper_resource.proto @@ -0,0 +1,2 @@ +syntax = "proto3"; +package deeper_resource; diff --git a/example/messages/sample_folder/sample_resource.proto b/example/messages/sample_folder/sample_resource.proto new file mode 100644 index 00000000..d80c0683 --- /dev/null +++ b/example/messages/sample_folder/sample_resource.proto @@ -0,0 +1,2 @@ +syntax = "proto3"; +package sample_resource; diff --git a/example/native/README.md b/example/native/README.md new file mode 100644 index 00000000..ba1f566a --- /dev/null +++ b/example/native/README.md @@ -0,0 +1,7 @@ +# Rust Crates + +This folder contains Rust crates. Entry point of the Rust logic is the `hub` library crate. These crates are integrated and compiled into the Flutter app by [Rust-In-Flutter](https://github.com/cunarist/rust-in-flutter) framework. + +- Do NOT change the name of the `hub` crate. Compilation presets expect the entry library crate to be located at `./native/hub`. +- Do NOT modify the `bridge` module inside `./native/hub/src` unless you know what you're doing. +- You CAN name crates other than `hub` as you want. diff --git a/example/native/hub/Cargo.toml b/example/native/hub/Cargo.toml new file mode 100644 index 00000000..96e94be6 --- /dev/null +++ b/example/native/hub/Cargo.toml @@ -0,0 +1,44 @@ +[package] +# Do not change the name of this crate. +name = "hub" +version = "0.1.0" +edition = "2021" + +[lib] +# `lib` is required for non-library targets, +# such as tests and benchmarks. +# `staticlib` is for iOS and macOS. +# `cdylib` is for all other platforms. +crate-type = ["lib", "cdylib", "staticlib"] + +# These are dependencies for non-web platforms. +[target.'cfg(not(target_family = "wasm"))'.dependencies] +libc = "0.2" +dart-sys = { version = "4.0.2" } +allo-isolate = { version = "0.1.20", features = ["zero-copy"] } +tokio = { version = "1.28.2", features = ["rt-multi-thread", "time"] } +os-thread-local = "0.1.3" + +# These are dependencies for the web. +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-bindgen = { version = "0.2.87" } +wasm-bindgen-futures = "0.4.37" +js-sys = "0.3.64" +web-sys = { version = "0.3.64", features = [ + "DedicatedWorkerGlobalScope", + "MessagePort", + "Blob", + "BlobPropertyBag", + "Worker", + "Url", + "BroadcastChannel", + "console", +] } +gloo-timers = { version = "0.3.0", features = ["futures"] } + +[dependencies] +bytemuck = "1.11.0" +lazy_static = "1.4.0" +tokio = { version = "1.28.2", features = ["sync", "macros"] } +prost = "0.12.0" +sample_crate = { path = "../sample_crate" } diff --git a/example/native/hub/src/bridge/api.rs b/example/native/hub/src/bridge/api.rs new file mode 100644 index 00000000..63411af1 --- /dev/null +++ b/example/native/hub/src/bridge/api.rs @@ -0,0 +1,173 @@ +#![allow(dead_code)] + +use crate::bridge::bridge_engine::StreamSink; +use lazy_static::lazy_static; +use std::cell::RefCell; +use std::sync::Arc; +use std::sync::Mutex; +use tokio::sync::mpsc::channel; +use tokio::sync::mpsc::Receiver; +use tokio::sync::mpsc::Sender; + +/// Available operations that a `RustRequest` object can hold. +/// There are 4 options, `Create`,`Read`,`Update`, and `Delete`. +pub enum RustOperation { + Create, + Read, + Update, + Delete, +} + +/// Holds the data that Rust streams to Dart. +#[derive(Clone)] +pub struct RustSignal { + pub resource: i32, + pub message: Option>, + pub blob: Option>, +} + +/// Request object that is sent from Dart to Rust. +pub struct RustRequest { + pub resource: i32, + pub operation: RustOperation, + pub message: Option>, + pub blob: Option>, +} + +/// Wrapper for `RustRequest` with a unique ID. +pub struct RustRequestUnique { + pub id: i32, + pub request: RustRequest, +} + +/// Response object that is sent from Rust to Dart. +#[derive(Clone)] +pub struct RustResponse { + pub successful: bool, + pub message: Option>, + pub blob: Option>, +} + +impl Default for RustResponse { + /// Empty response with the successful value of false. + fn default() -> RustResponse { + RustResponse { + successful: false, + message: None, + blob: None, + } + } +} + +/// Wrapper for `RustResponse` with a unique ID. +#[derive(Clone)] +pub struct RustResponseUnique { + pub id: i32, + pub response: RustResponse, +} + +type Cell = RefCell>; +type SharedCell = Arc>>; + +type RustSignalStream = StreamSink; +type RustResponseStream = StreamSink; +type RustRequestSender = Sender; +type RustRequestReceiver = Receiver; + +// Native: Main thread +// Web: Worker thread +thread_local! { + pub static REQUEST_SENDER: Cell = RefCell::new(None); +} + +// Native: `tokio` runtime threads +// Web: Worker thread +thread_local! { + pub static SIGNAL_STREAM: Cell = RefCell::new(None); + pub static RESPONSE_STREAM: Cell = RefCell::new(None); + pub static RESPONSE_SENDER: Cell = RefCell::new(None); +} + +// Native: All threads +// Web: Worker thread +lazy_static! { + pub static ref SIGNAL_STREAM_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); + pub static ref RESPONSE_STREAM_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); + pub static ref REQUST_RECEIVER_SHARED: SharedCell = + Arc::new(Mutex::new(RefCell::new(None))); +} + +#[cfg(not(target_family = "wasm"))] +lazy_static! { + pub static ref TOKIO_RUNTIME: os_thread_local::ThreadLocal>> = + os_thread_local::ThreadLocal::new(|| RefCell::new(None)); +} + +/// Returns a stream object in Dart that listens to Rust. +pub fn prepare_rust_signal_stream(signal_stream: StreamSink) { + let cell = SIGNAL_STREAM_SHARED.lock().unwrap(); + cell.replace(Some(signal_stream)); +} + +/// Returns a stream object in Dart that returns responses from Rust. +pub fn prepare_rust_response_stream(response_stream: StreamSink) { + let cell = RESPONSE_STREAM_SHARED.lock().unwrap(); + cell.replace(Some(response_stream)); +} + +/// Prepare channels that are used in the Rust world. +pub fn prepare_channels() { + let (request_sender, request_receiver) = channel(1024); + REQUEST_SENDER.with(move |inner| { + inner.replace(Some(request_sender)); + }); + let cell = REQUST_RECEIVER_SHARED.lock().unwrap(); + cell.replace(Some(request_receiver)); +} + +/// Check if the streams are ready in Rust. +/// This should be done before starting the Rust logic. +pub fn check_rust_streams() -> bool { + let mut are_all_ready = true; + let cell = SIGNAL_STREAM_SHARED.lock().unwrap(); + if cell.borrow().is_none() { + are_all_ready = false; + }; + let cell = RESPONSE_STREAM_SHARED.lock().unwrap(); + if cell.borrow().is_none() { + are_all_ready = false; + }; + are_all_ready +} + +/// Start the main function of Rust. +pub fn start_rust_logic() { + #[cfg(not(target_family = "wasm"))] + { + TOKIO_RUNTIME.with(move |inner| { + let tokio_runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + tokio_runtime.spawn(crate::main()); + inner.replace(Some(tokio_runtime)); + }); + } + #[cfg(target_family = "wasm")] + { + #[cfg(debug_assertions)] + crate::bridge::bridge_engine::wasm_bindgen_src::worker::replace_worker(); + wasm_bindgen_futures::spawn_local(crate::main()); + } +} + +/// Send a request to Rust and receive a response in Dart. +pub fn request_to_rust(request_unique: RustRequestUnique) { + REQUEST_SENDER.with(move |inner| { + let borrowed = inner.borrow(); + let sender = borrowed.as_ref().unwrap(); + sender.try_send(request_unique).ok(); + }); +} diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn b/example/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn new file mode 100644 index 00000000..2b10262f --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +import("../../sdk_args.gni") + +# This rule copies header files to include/ +copy("copy_headers") { + visibility = [ "../../sdk:copy_headers" ] + + sources = [ + "dart_api.h", + "dart_api_dl.c", + "dart_api_dl.h", + "dart_native_api.h", + "dart_tools_api.h", + "dart_version.h", + "internal/dart_api_dl_impl.h", + ] + + outputs = + [ "$root_out_dir/$dart_sdk_output/include/{{source_target_relative}}" ] +} diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/README.md b/example/native/hub/src/bridge/bridge_engine/dart_api/README.md new file mode 100644 index 00000000..fb631491 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/README.md @@ -0,0 +1,6 @@ +This folder contains a copy of Dart SDK [include/](https://github.com/dart-lang/sdk/tree/master/runtime/include) folder. + +Current version of Dart API is `2.0`. On breaking changes the major version is increased. Minor versions are allowed to be +different. If the DartVM has a higher minor version, it will provide more symbols than we initialize here. + +Note that you might need to update if Dart SDK makes an incompatible change to its DL C API. diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h new file mode 100644 index 00000000..0e68d5cc --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/analyze_snapshot_api.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_ +#define RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_ + +#include + +namespace dart { +namespace snapshot_analyzer { +typedef struct { + const uint8_t* vm_snapshot_data; + const uint8_t* vm_snapshot_instructions; + const uint8_t* vm_isolate_data; + const uint8_t* vm_isolate_instructions; +} Dart_SnapshotAnalyzerInformation; + +void Dart_DumpSnapshotInformationAsJson(char** buffer, + intptr_t* buffer_length, + Dart_SnapshotAnalyzerInformation* info); + +void Dart_DumpSnapshotInformationPP(Dart_SnapshotAnalyzerInformation* info); + +} // namespace snapshot_analyzer +} // namespace dart + +#endif // RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h new file mode 100644 index 00000000..cc647976 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/bin/dart_io_api.h @@ -0,0 +1,69 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_INCLUDE_BIN_DART_IO_API_H_ +#define RUNTIME_INCLUDE_BIN_DART_IO_API_H_ + +#include "dart_tools_api.h" + +namespace dart { +namespace bin { + +// Bootstraps 'dart:io'. +void BootstrapDartIo(); + +// Cleans up 'dart:io'. +void CleanupDartIo(); + +// Lets dart:io know where the system temporary directory is located. +// Currently only wired up on Android. +void SetSystemTempDirectory(const char* system_temp); + +// Tells the system whether to capture Stdout events. +void SetCaptureStdout(bool value); + +// Tells the system whether to capture Stderr events. +void SetCaptureStderr(bool value); + +// Should Stdout events be captured? +bool ShouldCaptureStdout(); + +// Should Stderr events be captured? +bool ShouldCaptureStderr(); + +// Set the executable name used by Platform.executable. +void SetExecutableName(const char* executable_name); + +// Set the arguments used by Platform.executableArguments. +void SetExecutableArguments(int script_index, char** argv); + +// Set dart:io implementation specific fields of Dart_EmbedderInformation. +void GetIOEmbedderInformation(Dart_EmbedderInformation* info); + +// Appropriate to assign to Dart_InitializeParams.file_open/read/write/close. +void* OpenFile(const char* name, bool write); +void ReadFile(uint8_t** data, intptr_t* file_len, void* stream); +void WriteFile(const void* buffer, intptr_t num_bytes, void* stream); +void CloseFile(void* stream); + +// Generates 'length' random bytes into 'buffer'. Returns true on success +// and false on failure. This is appropriate to assign to +// Dart_InitializeParams.entropy_source. +bool GetEntropy(uint8_t* buffer, intptr_t length); + +// Performs a lookup of the I/O Dart_NativeFunction with a specified 'name' and +// 'argument_count'. Returns NULL if no I/O native function with a matching +// name and parameter count is found. +Dart_NativeFunction LookupIONative(Dart_Handle name, + int argument_count, + bool* auto_setup_scope); + +// Returns the symbol for I/O native function 'nf'. Returns NULL if 'nf' is not +// a valid I/O native function. +const uint8_t* LookupIONativeSymbol(Dart_NativeFunction nf); + +} // namespace bin +} // namespace dart + +#endif // RUNTIME_INCLUDE_BIN_DART_IO_API_H_ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h new file mode 100644 index 00000000..73e5dad7 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api.h @@ -0,0 +1,4168 @@ +/* + * Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_API_H_ +#define RUNTIME_INCLUDE_DART_API_H_ + +/** \mainpage Dart Embedding API Reference + * + * This reference describes the Dart Embedding API, which is used to embed the + * Dart Virtual Machine within C/C++ applications. + * + * This reference is generated from the header include/dart_api.h. + */ + +/* __STDC_FORMAT_MACROS has to be defined before including to + * enable platform independent printf format specifiers. */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include + +#ifdef __cplusplus +#define DART_EXTERN_C extern "C" +#else +#define DART_EXTERN_C extern +#endif + +#if defined(__CYGWIN__) +#error Tool chain and platform not supported. +#elif defined(_WIN32) +#if defined(DART_SHARED_LIB) +#define DART_EXPORT DART_EXTERN_C __declspec(dllexport) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#if __GNUC__ >= 4 +#if defined(DART_SHARED_LIB) +#define DART_EXPORT \ + DART_EXTERN_C __attribute__((visibility("default"))) __attribute((used)) +#else +#define DART_EXPORT DART_EXTERN_C +#endif +#else +#error Tool chain not supported. +#endif +#endif + +#if __GNUC__ +#define DART_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif _MSC_VER +#define DART_WARN_UNUSED_RESULT _Check_return_ +#else +#define DART_WARN_UNUSED_RESULT +#endif + +/* + * ======= + * Handles + * ======= + */ + +/** + * An isolate is the unit of concurrency in Dart. Each isolate has + * its own memory and thread of control. No state is shared between + * isolates. Instead, isolates communicate by message passing. + * + * Each thread keeps track of its current isolate, which is the + * isolate which is ready to execute on the current thread. The + * current isolate may be NULL, in which case no isolate is ready to + * execute. Most of the Dart apis require there to be a current + * isolate in order to function without error. The current isolate is + * set by any call to Dart_CreateIsolateGroup or Dart_EnterIsolate. + */ +typedef struct _Dart_Isolate* Dart_Isolate; +typedef struct _Dart_IsolateGroup* Dart_IsolateGroup; + +/** + * An object reference managed by the Dart VM garbage collector. + * + * Because the garbage collector may move objects, it is unsafe to + * refer to objects directly. Instead, we refer to objects through + * handles, which are known to the garbage collector and updated + * automatically when the object is moved. Handles should be passed + * by value (except in cases like out-parameters) and should never be + * allocated on the heap. + * + * Most functions in the Dart Embedding API return a handle. When a + * function completes normally, this will be a valid handle to an + * object in the Dart VM heap. This handle may represent the result of + * the operation or it may be a special valid handle used merely to + * indicate successful completion. Note that a valid handle may in + * some cases refer to the null object. + * + * --- Error handles --- + * + * When a function encounters a problem that prevents it from + * completing normally, it returns an error handle (See Dart_IsError). + * An error handle has an associated error message that gives more + * details about the problem (See Dart_GetError). + * + * There are four kinds of error handles that can be produced, + * depending on what goes wrong: + * + * - Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * - Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * Prototypically this would occur during a call to Dart_Invoke, but + * it can occur in any function which triggers the execution of Dart + * code (for example, Dart_ToString). + * + * An unhandled exception error provides access to an exception and + * stacktrace via the functions Dart_ErrorGetException and + * Dart_ErrorGetStackTrace. + * + * - Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. As above, this can + * occur in any function which triggers the execution of Dart code. + * + * - Fatal error handles are produced when the system wants to shut + * down the current isolate. + * + * --- Propagating errors --- + * + * When an error handle is returned from the top level invocation of + * Dart code in a program, the embedder must handle the error as they + * see fit. Often, the embedder will print the error message produced + * by Dart_Error and exit the program. + * + * When an error is returned while in the body of a native function, + * it can be propagated up the call stack by calling + * Dart_PropagateError, Dart_SetReturnValue, or Dart_ThrowException. + * Errors should be propagated unless there is a specific reason not + * to. If an error is not propagated then it is ignored. For + * example, if an unhandled exception error is ignored, that + * effectively "catches" the unhandled exception. Fatal errors must + * always be propagated. + * + * When an error is propagated, any current scopes created by + * Dart_EnterScope will be exited. + * + * Using Dart_SetReturnValue to propagate an exception is somewhat + * more convenient than using Dart_PropagateError, and should be + * preferred for reasons discussed below. + * + * Dart_PropagateError and Dart_ThrowException do not return. Instead + * they transfer control non-locally using a setjmp-like mechanism. + * This can be inconvenient if you have resources that you need to + * clean up before propagating the error. + * + * When relying on Dart_PropagateError, we often return error handles + * rather than propagating them from helper functions. Consider the + * following contrived example: + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result; + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_Handle result = isLongStringHelper(arg); + * 15 if (Dart_IsError(result)) { + * 16 FreeMyResource(); + * 17 Dart_PropagateError(result); + * 18 abort(); // will not reach here + * 19 } + * 20 Dart_SetReturnValue(result); + * 21 FreeMyResource(); + * 22 Dart_ExitScope(); + * 23 } + * + * In this example, we have a native function which calls a helper + * function to do its work. On line 5, the helper function could call + * Dart_PropagateError, but that would not give the native function a + * chance to call FreeMyResource(), causing a leak. Instead, the + * helper function returns the error handle to the caller, giving the + * caller a chance to clean up before propagating the error handle. + * + * When an error is propagated by calling Dart_SetReturnValue, the + * native function will be allowed to complete normally and then the + * exception will be propagated only once the native call + * returns. This can be convenient, as it allows the C code to clean + * up normally. + * + * The example can be written more simply using Dart_SetReturnValue to + * propagate the error. + * + * 1 Dart_Handle isLongStringHelper(Dart_Handle arg) { + * 2 intptr_t* length = 0; + * 3 result = Dart_StringLength(arg, &length); + * 4 if (Dart_IsError(result)) { + * 5 return result + * 6 } + * 7 return Dart_NewBoolean(length > 100); + * 8 } + * 9 + * 10 void NativeFunction_isLongString(Dart_NativeArguments args) { + * 11 Dart_EnterScope(); + * 12 AllocateMyResource(); + * 13 Dart_Handle arg = Dart_GetNativeArgument(args, 0); + * 14 Dart_SetReturnValue(isLongStringHelper(arg)); + * 15 FreeMyResource(); + * 16 Dart_ExitScope(); + * 17 } + * + * In this example, the call to Dart_SetReturnValue on line 14 will + * either return the normal return value or the error (potentially + * generated on line 3). The call to FreeMyResource on line 15 will + * execute in either case. + * + * --- Local and persistent handles --- + * + * Local handles are allocated within the current scope (see + * Dart_EnterScope) and go away when the current scope exits. Unless + * otherwise indicated, callers should assume that all functions in + * the Dart embedding api return local handles. + * + * Persistent handles are allocated within the current isolate. They + * can be used to store objects across scopes. Persistent handles have + * the lifetime of the current isolate unless they are explicitly + * deallocated (see Dart_DeletePersistentHandle). + * The type Dart_Handle represents a handle (both local and persistent). + * The type Dart_PersistentHandle is a Dart_Handle and it is used to + * document that a persistent handle is expected as a parameter to a call + * or the return value from a call is a persistent handle. + * + * FinalizableHandles are persistent handles which are auto deleted when + * the object is garbage collected. It is never safe to use these handles + * unless you know the object is still reachable. + * + * WeakPersistentHandles are persistent handles which are automatically set + * to point Dart_Null when the object is garbage collected. They are not auto + * deleted, so it is safe to use them after the object has become unreachable. + */ +typedef struct _Dart_Handle* Dart_Handle; +typedef Dart_Handle Dart_PersistentHandle; +typedef struct _Dart_WeakPersistentHandle* Dart_WeakPersistentHandle; +typedef struct _Dart_FinalizableHandle* Dart_FinalizableHandle; +// These structs are versioned by DART_API_DL_MAJOR_VERSION, bump the +// version when changing this struct. + +typedef void (*Dart_HandleFinalizer)(void* isolate_callback_data, void* peer); + +/** + * Is this an error handle? + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsError(Dart_Handle handle); + +/** + * Is this an api error handle? + * + * Api error handles are produced when an api function is misused. + * This happens when a Dart embedding api function is called with + * invalid arguments or in an invalid context. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsApiError(Dart_Handle handle); + +/** + * Is this an unhandled exception error handle? + * + * Unhandled exception error handles are produced when, during the + * execution of Dart code, an exception is thrown but not caught. + * This can occur in any function which triggers the execution of Dart + * code. + * + * See Dart_ErrorGetException and Dart_ErrorGetStackTrace. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsUnhandledExceptionError(Dart_Handle handle); + +/** + * Is this a compilation error handle? + * + * Compilation error handles are produced when, during the execution + * of Dart code, a compile-time error occurs. This can occur in any + * function which triggers the execution of Dart code. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsCompilationError(Dart_Handle handle); + +/** + * Is this a fatal error handle? + * + * Fatal error handles are produced when the system wants to shut down + * the current isolate. + * + * Requires there to be a current isolate. + */ +DART_EXPORT bool Dart_IsFatalError(Dart_Handle handle); + +/** + * Gets the error message from an error handle. + * + * Requires there to be a current isolate. + * + * \return A C string containing an error message if the handle is + * error. An empty C string ("") if the handle is valid. This C + * String is scope allocated and is only valid until the next call + * to Dart_ExitScope. +*/ +DART_EXPORT const char* Dart_GetError(Dart_Handle handle); + +/** + * Is this an error handle for an unhandled exception? + */ +DART_EXPORT bool Dart_ErrorHasException(Dart_Handle handle); + +/** + * Gets the exception Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetException(Dart_Handle handle); + +/** + * Gets the stack trace Object from an unhandled exception error handle. + */ +DART_EXPORT Dart_Handle Dart_ErrorGetStackTrace(Dart_Handle handle); + +/** + * Produces an api error handle with the provided error message. + * + * Requires there to be a current isolate. + * + * \param error the error message. + */ +DART_EXPORT Dart_Handle Dart_NewApiError(const char* error); +DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error); + +/** + * Produces a new unhandled exception error handle. + * + * Requires there to be a current isolate. + * + * \param exception An instance of a Dart object to be thrown or + * an ApiError or CompilationError handle. + * When an ApiError or CompilationError handle is passed in + * a string object of the error message is created and it becomes + * the Dart object to be thrown. + */ +DART_EXPORT Dart_Handle Dart_NewUnhandledExceptionError(Dart_Handle exception); + +/** + * Propagates an error. + * + * If the provided handle is an unhandled exception error, this + * function will cause the unhandled exception to be rethrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If the error is not an unhandled exception error, we will unwind + * the stack to the next C frame. Intervening Dart frames will be + * discarded; specifically, 'finally' blocks will not execute. This + * is the standard way that compilation errors (and the like) are + * handled by the Dart runtime. + * + * In either case, when an error is propagated any current scopes + * created by Dart_EnterScope will be exited. + * + * See the additional discussion under "Propagating Errors" at the + * beginning of this file. + * + * \param handle An error handle (See Dart_IsError) + * + * On success, this function does not return. On failure, the + * process is terminated. + */ +DART_EXPORT void Dart_PropagateError(Dart_Handle handle); + +/** + * Converts an object to a string. + * + * May generate an unhandled exception error. + * + * \return The converted string if no error occurs during + * the conversion. If an error does occur, an error handle is + * returned. + */ +DART_EXPORT Dart_Handle Dart_ToString(Dart_Handle object); + +/** + * Checks to see if two handles refer to identically equal objects. + * + * If both handles refer to instances, this is equivalent to using the top-level + * function identical() from dart:core. Otherwise, returns whether the two + * argument handles refer to the same object. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * + * \return True if the objects are identically equal. False otherwise. + */ +DART_EXPORT bool Dart_IdentityEquals(Dart_Handle obj1, Dart_Handle obj2); + +/** + * Allocates a handle in the current scope from a persistent handle. + */ +DART_EXPORT Dart_Handle Dart_HandleFromPersistent(Dart_PersistentHandle object); + +/** + * Allocates a handle in the current scope from a weak persistent handle. + * + * This will be a handle to Dart_Null if the object has been garbage collected. + */ +DART_EXPORT Dart_Handle +Dart_HandleFromWeakPersistent(Dart_WeakPersistentHandle object); + +/** + * Allocates a persistent handle for an object. + * + * This handle has the lifetime of the current isolate unless it is + * explicitly deallocated by calling Dart_DeletePersistentHandle. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_PersistentHandle Dart_NewPersistentHandle(Dart_Handle object); + +/** + * Assign value of local handle to a persistent handle. + * + * Requires there to be a current isolate. + * + * \param obj1 A persistent handle whose value needs to be set. + * \param obj2 An object whose value needs to be set to the persistent handle. + */ +DART_EXPORT void Dart_SetPersistentHandle(Dart_PersistentHandle obj1, + Dart_Handle obj2); + +/** + * Deallocates a persistent handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeletePersistentHandle(Dart_PersistentHandle object); + +/** + * Allocates a weak persistent handle for an object. + * + * This handle has the lifetime of the current isolate. The handle can also be + * explicitly deallocated by calling Dart_DeleteWeakPersistentHandle. + * + * If the object becomes unreachable the callback is invoked with the peer as + * argument. The callback can be executed on any thread, will have a current + * isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. This + * gives the embedder the ability to cleanup data associated with the object. + * The handle will point to the Dart_Null object after the finalizer has been + * run. It is illegal to call into the VM with any other Dart_* functions from + * the callback. If the handle is deleted before the object becomes + * unreachable, the callback is never invoked. + * + * Requires there to be a current isolate. + * + * \param object An object with identity. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The weak persistent handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_WeakPersistentHandle +Dart_NewWeakPersistentHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given weak persistent [object] handle. + * + * Requires there to be a current isolate group. + */ +DART_EXPORT void Dart_DeleteWeakPersistentHandle( + Dart_WeakPersistentHandle object); + +/** + * Updates the external memory size for the given weak persistent handle. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateExternalSize(Dart_WeakPersistentHandle object, + intptr_t external_allocation_size); + +/** + * Allocates a finalizable handle for an object. + * + * This handle has the lifetime of the current isolate group unless the object + * pointed to by the handle is garbage collected, in this case the VM + * automatically deletes the handle after invoking the callback associated + * with the handle. The handle can also be explicitly deallocated by + * calling Dart_DeleteFinalizableHandle. + * + * If the object becomes unreachable the callback is invoked with the + * the peer as argument. The callback can be executed on any thread, will have + * an isolate group, but will not have a current isolate. The callback can only + * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. + * This gives the embedder the ability to cleanup data associated with the + * object and clear out any cached references to the handle. All references to + * this handle after the callback will be invalid. It is illegal to call into + * the VM with any other Dart_* functions from the callback. If the handle is + * deleted before the object becomes unreachable, the callback is never + * invoked. + * + * Requires there to be a current isolate. + * + * \param object An object with identity. + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The finalizable handle or NULL. NULL is returned in case of bad + * parameters. + */ +DART_EXPORT Dart_FinalizableHandle +Dart_NewFinalizableHandle(Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Deletes the given finalizable [object] handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_DeleteFinalizableHandle(Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object); + +/** + * Updates the external memory size for the given finalizable handle. + * + * The caller has to provide the actual Dart object the handle was created from + * to prove the object (and therefore the finalizable handle) is still alive. + * + * May trigger garbage collection. + */ +DART_EXPORT void Dart_UpdateFinalizableExternalSize( + Dart_FinalizableHandle object, + Dart_Handle strong_ref_to_object, + intptr_t external_allocation_size); + +/* + * ========================== + * Initialization and Globals + * ========================== + */ + +/** + * Gets the version string for the Dart VM. + * + * The version of the Dart VM can be accessed without initializing the VM. + * + * \return The version string for the embedded Dart VM. + */ +DART_EXPORT const char* Dart_VersionString(void); + +/** + * Isolate specific flags are set when creating a new isolate using the + * Dart_IsolateFlags structure. + * + * Current version of flags is encoded in a 32-bit integer with 16 bits used + * for each part. + */ + +#define DART_FLAGS_CURRENT_VERSION (0x0000000c) + +typedef struct { + int32_t version; + bool enable_asserts; + bool use_field_guards; + bool use_osr; + bool obfuscate; + bool load_vmservice_library; + bool copy_parent_code; + bool null_safety; + bool is_system_isolate; + bool snapshot_is_dontneed_safe; + bool branch_coverage; +} Dart_IsolateFlags; + +/** + * Initialize Dart_IsolateFlags with correct version and default values. + */ +DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags); + +/** + * An isolate creation and initialization callback function. + * + * This callback, provided by the embedder, is called when the VM + * needs to create an isolate. The callback should create an isolate + * by calling Dart_CreateIsolateGroup and load any scripts required for + * execution. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns NULL, it is the responsibility of this + * function to ensure that Dart_ShutdownIsolate has been called if + * required (for example, if the isolate was created successfully by + * Dart_CreateIsolateGroup() but the root library fails to load + * successfully, then the function should call Dart_ShutdownIsolate + * before returning). + * + * When the function returns NULL, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param script_uri The uri of the main source file or snapshot to load. + * Either the URI of the parent isolate set in Dart_CreateIsolateGroup for + * Isolate.spawn, or the argument to Isolate.spawnUri canonicalized by the + * library tag handler of the parent isolate. + * The callback is responsible for loading the program by a call to + * Dart_LoadScriptFromKernel. + * \param main The name of the main entry point this isolate will + * eventually run. This is provided for advisory purposes only to + * improve debugging messages. The main function is not invoked by + * this function. + * \param package_root Ignored. + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for this isolate + * to resolve package imports against. If this parameter is not passed the + * package resolution of the parent isolate should be used. + * \param flags Default flags for this isolate being spawned. Either inherited + * from the spawning isolate or passed as parameters when spawning the + * isolate from Dart code. + * \param isolate_data The isolate data which was passed to the + * parent isolate when it was created by calling Dart_CreateIsolateGroup(). + * \param error A structure into which the embedder can place a + * C string containing an error message in the case of failures. + * + * \return The embedder returns NULL if the creation and + * initialization was not successful and the isolate if successful. + */ +typedef Dart_Isolate (*Dart_IsolateGroupCreateCallback)( + const char* script_uri, + const char* main, + const char* package_root, + const char* package_config, + Dart_IsolateFlags* flags, + void* isolate_data, + char** error); + +/** + * An isolate initialization callback function. + * + * This callback, provided by the embedder, is called when the VM has created an + * isolate within an existing isolate group (i.e. from the same source as an + * existing isolate). + * + * The callback should setup native resolvers and might want to set a custom + * message handler via [Dart_SetMessageNotifyCallback] and mark the isolate as + * runnable. + * + * This callback may be called on a different thread than the one + * running the parent isolate. + * + * When the function returns `false`, it is the responsibility of this + * function to ensure that `Dart_ShutdownIsolate` has been called. + * + * When the function returns `false`, the function should set *error to + * a malloc-allocated buffer containing a useful error message. The + * caller of this function (the VM) will make sure that the buffer is + * freed. + * + * \param child_isolate_data The callback data to associate with the new + * child isolate. + * \param error A structure into which the embedder can place a + * C string containing an error message in the case the initialization fails. + * + * \return The embedder returns true if the initialization was successful and + * false otherwise (in which case the VM will terminate the isolate). + */ +typedef bool (*Dart_InitializeIsolateCallback)(void** child_isolate_data, + char** error); + +/** + * An isolate shutdown callback function. + * + * This callback, provided by the embedder, is called before the vm + * shuts down an isolate. The isolate being shutdown will be the current + * isolate. It is safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateShutdownCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate. There will be no current isolate and it is *not* + * safe to run Dart code. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * \param isolate_data The same callback data which was passed to the isolate + * when it was created. + */ +typedef void (*Dart_IsolateCleanupCallback)(void* isolate_group_data, + void* isolate_data); + +/** + * An isolate group cleanup callback function. + * + * This callback, provided by the embedder, is called after the vm + * shuts down an isolate group. + * + * This function should be used to dispose of native resources that + * are allocated to an isolate in order to avoid leaks. + * + * \param isolate_group_data The same callback data which was passed to the + * isolate group when it was created. + * + */ +typedef void (*Dart_IsolateGroupCleanupCallback)(void* isolate_group_data); + +/** + * A thread start callback function. + * This callback, provided by the embedder, is called after a thread in the + * vm thread pool starts. + * This function could be used to adjust thread priority or attach native + * resources to the thread. + */ +typedef void (*Dart_ThreadStartCallback)(void); + +/** + * A thread death callback function. + * This callback, provided by the embedder, is called before a thread in the + * vm thread pool exits. + * This function could be used to dispose of native resources that + * are associated and attached to the thread, in order to avoid leaks. + */ +typedef void (*Dart_ThreadExitCallback)(void); + +/** + * Opens a file for reading or writing. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param name The name of the file to open. + * \param write A boolean variable which indicates if the file is to + * opened for writing. If there is an existing file it needs to truncated. + */ +typedef void* (*Dart_FileOpenCallback)(const char* name, bool write); + +/** + * Read contents of file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param data Buffer allocated in the callback into which the contents + * of the file are read into. It is the responsibility of the caller to + * free this buffer. + * \param file_length A variable into which the length of the file is returned. + * In the case of an error this value would be -1. + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileReadCallback)(uint8_t** data, + intptr_t* file_length, + void* stream); + +/** + * Write data into file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param data Buffer which needs to be written into the file. + * \param length Length of the buffer. + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileWriteCallback)(const void* data, + intptr_t length, + void* stream); + +/** + * Closes the opened file. + * + * Callback provided by the embedder for file operations. If the + * embedder does not allow file operations this callback can be + * NULL. + * + * \param stream Handle to the opened file. + */ +typedef void (*Dart_FileCloseCallback)(void* stream); + +typedef bool (*Dart_EntropySource)(uint8_t* buffer, intptr_t length); + +/** + * Callback provided by the embedder that is used by the vmservice isolate + * to request the asset archive. The asset archive must be an uncompressed tar + * archive that is stored in a Uint8List. + * + * If the embedder has no vmservice isolate assets, the callback can be NULL. + * + * \return The embedder must return a handle to a Uint8List containing an + * uncompressed tar archive or null. + */ +typedef Dart_Handle (*Dart_GetVMServiceAssetsArchive)(void); + +/** + * The current version of the Dart_InitializeFlags. Should be incremented every + * time Dart_InitializeFlags changes in a binary incompatible way. + */ +#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000007) + +/** Forward declaration */ +struct Dart_CodeObserver; + +/** + * Callback provided by the embedder that is used by the VM to notify on code + * object creation, *before* it is invoked the first time. + * This is useful for embedders wanting to e.g. keep track of PCs beyond + * the lifetime of the garbage collected code objects. + * Note that an address range may be used by more than one code object over the + * lifecycle of a process. Clients of this function should record timestamps for + * these compilation events and when collecting PCs to disambiguate reused + * address ranges. + */ +typedef void (*Dart_OnNewCodeCallback)(struct Dart_CodeObserver* observer, + const char* name, + uintptr_t base, + uintptr_t size); + +typedef struct Dart_CodeObserver { + void* data; + + Dart_OnNewCodeCallback on_new_code; +} Dart_CodeObserver; + +/** + * Optional callback provided by the embedder that is used by the VM to + * implement registration of kernel blobs for the subsequent Isolate.spawnUri + * If no callback is provided, the registration of kernel blobs will throw + * an error. + * + * \param kernel_buffer A buffer which contains a kernel program. Callback + * should copy the contents of `kernel_buffer` as + * it may be freed immediately after registration. + * \param kernel_buffer_size The size of `kernel_buffer`. + * + * \return A C string representing URI which can be later used + * to spawn a new isolate. This C String should be scope allocated + * or owned by the embedder. + * Returns NULL if embedder runs out of memory. + */ +typedef const char* (*Dart_RegisterKernelBlobCallback)( + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +/** + * Optional callback provided by the embedder that is used by the VM to + * unregister kernel blobs. + * If no callback is provided, the unregistration of kernel blobs will throw + * an error. + * + * \param kernel_blob_uri URI of the kernel blob to unregister. + */ +typedef void (*Dart_UnregisterKernelBlobCallback)(const char* kernel_blob_uri); + +/** + * Describes how to initialize the VM. Used with Dart_Initialize. + */ +typedef struct { + /** + * Identifies the version of the struct used by the client. + * should be initialized to DART_INITIALIZE_PARAMS_CURRENT_VERSION. + */ + int32_t version; + + /** + * A buffer containing snapshot data, or NULL if no snapshot is provided. + * + * If provided, the buffer must remain valid until Dart_Cleanup returns. + */ + const uint8_t* vm_snapshot_data; + + /** + * A buffer containing a snapshot of precompiled instructions, or NULL if + * no snapshot is provided. + * + * If provided, the buffer must remain valid until Dart_Cleanup returns. + */ + const uint8_t* vm_snapshot_instructions; + + /** + * A function to be called during isolate group creation. + * See Dart_IsolateGroupCreateCallback. + */ + Dart_IsolateGroupCreateCallback create_group; + + /** + * A function to be called during isolate + * initialization inside an existing isolate group. + * See Dart_InitializeIsolateCallback. + */ + Dart_InitializeIsolateCallback initialize_isolate; + + /** + * A function to be called right before an isolate is shutdown. + * See Dart_IsolateShutdownCallback. + */ + Dart_IsolateShutdownCallback shutdown_isolate; + + /** + * A function to be called after an isolate was shutdown. + * See Dart_IsolateCleanupCallback. + */ + Dart_IsolateCleanupCallback cleanup_isolate; + + /** + * A function to be called after an isolate group is + * shutdown. See Dart_IsolateGroupCleanupCallback. + */ + Dart_IsolateGroupCleanupCallback cleanup_group; + + Dart_ThreadStartCallback thread_start; + Dart_ThreadExitCallback thread_exit; + Dart_FileOpenCallback file_open; + Dart_FileReadCallback file_read; + Dart_FileWriteCallback file_write; + Dart_FileCloseCallback file_close; + Dart_EntropySource entropy_source; + + /** + * A function to be called by the service isolate when it requires the + * vmservice assets archive. See Dart_GetVMServiceAssetsArchive. + */ + Dart_GetVMServiceAssetsArchive get_service_assets; + + bool start_kernel_isolate; + + /** + * An external code observer callback function. The observer can be invoked + * as early as during the Dart_Initialize() call. + */ + Dart_CodeObserver* code_observer; + + /** + * Kernel blob registration callback function. See Dart_RegisterKernelBlobCallback. + */ + Dart_RegisterKernelBlobCallback register_kernel_blob; + + /** + * Kernel blob unregistration callback function. See Dart_UnregisterKernelBlobCallback. + */ + Dart_UnregisterKernelBlobCallback unregister_kernel_blob; +} Dart_InitializeParams; + +/** + * Initializes the VM. + * + * \param params A struct containing initialization information. The version + * field of the struct must be DART_INITIALIZE_PARAMS_CURRENT_VERSION. + * + * \return NULL if initialization is successful. Returns an error message + * otherwise. The caller is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Initialize( + Dart_InitializeParams* params); + +/** + * Cleanup state in the VM before process termination. + * + * \return NULL if cleanup is successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This function must not be called on a thread that was created by the VM + * itself. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Cleanup(void); + +/** + * Sets command line flags. Should be called before Dart_Initialize. + * + * \param argc The length of the arguments array. + * \param argv An array of arguments. + * + * \return NULL if successful. Returns an error message otherwise. + * The caller is responsible for freeing the error message. + * + * NOTE: This call does not store references to the passed in c-strings. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_SetVMFlags(int argc, + const char** argv); + +/** + * Returns true if the named VM flag is of boolean type, specified, and set to + * true. + * + * \param flag_name The name of the flag without leading punctuation + * (example: "enable_asserts"). + */ +DART_EXPORT bool Dart_IsVMFlagSet(const char* flag_name); + +/* + * ======== + * Isolates + * ======== + */ + +/** + * Creates a new isolate. The new isolate becomes the current isolate. + * + * A snapshot can be used to restore the VM quickly to a saved state + * and is useful for fast startup. If snapshot data is provided, the + * isolate will be started using that snapshot data. Requires a core snapshot or + * an app snapshot created by Dart_CreateSnapshot or + * Dart_CreatePrecompiledSnapshot* from a VM with the same version. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a + * child isolate is created by Isolate.spawn. The embedder should use a URI + * that allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param isolate_snapshot_data Buffer containing the snapshot data of the + * isolate or NULL if no snapshot is provided. If provided, the buffer must + * remain valid until the isolate shuts down. + * \param isolate_snapshot_instructions Buffer containing the snapshot + * instructions of the isolate or NULL if no snapshot is provided. If + * provided, the buffer must remain valid until the isolate shuts down. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroup(const char* script_uri, + const char* name, + const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Creates a new isolate inside the isolate group of [group_member]. + * + * Requires there to be no current isolate. + * + * \param group_member An isolate from the same group into which the newly created + * isolate should be born into. Other threads may not have entered / enter this + * member isolate. + * \param name A short name for the isolate for debugging purposes. + * \param shutdown_callback A callback to be called when the isolate is being + * shutdown (may be NULL). + * \param cleanup_callback A callback to be called when the isolate is being + * cleaned up (may be NULL). + * \param child_isolate_data The embedder-specific data associated with this isolate. + * \param error Set to NULL if creation is successful, set to an error + * message otherwise. The caller is responsible for calling free() on the + * error message. + * + * \return The newly created isolate on success, or NULL if isolate creation + * failed. + * + * If successful, the newly created isolate will become the current isolate. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateInGroup(Dart_Isolate group_member, + const char* name, + Dart_IsolateShutdownCallback shutdown_callback, + Dart_IsolateCleanupCallback cleanup_callback, + void* child_isolate_data, + char** error); + +/* TODO(turnidge): Document behavior when there is already a current + * isolate. */ + +/** + * Creates a new isolate from a Dart Kernel file. The new isolate + * becomes the current isolate. + * + * Requires there to be no current isolate. + * + * \param script_uri The main source file or snapshot this isolate will load. + * The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a + * child isolate is created by Isolate.spawn. The embedder should use a URI that + * allows it to load the same program into such a child isolate. + * \param name A short name for the isolate to improve debugging messages. + * Typically of the format 'foo.dart:main()'. + * \param kernel_buffer A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * \param kernel_buffer_size The size of `kernel_buffer`. + * \param flags Pointer to VM specific flags or NULL for default flags. + * \param isolate_group_data Embedder group data. This data can be obtained + * by calling Dart_IsolateGroupData and will be passed to the + * Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and + * Dart_IsolateGroupCleanupCallback. + * \param isolate_data Embedder data. This data will be passed to + * the Dart_IsolateGroupCreateCallback when new isolates are spawned from + * this parent isolate. + * \param error Returns NULL if creation is successful, an error message + * otherwise. The caller is responsible for calling free() on the error + * message. + * + * \return The new isolate on success, or NULL if isolate creation failed. + */ +DART_EXPORT Dart_Isolate +Dart_CreateIsolateGroupFromKernel(const char* script_uri, + const char* name, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size, + Dart_IsolateFlags* flags, + void* isolate_group_data, + void* isolate_data, + char** error); +/** + * Shuts down the current isolate. After this call, the current isolate is NULL. + * Any current scopes created by Dart_EnterScope will be exited. Invokes the + * shutdown callback and any callbacks of remaining weak persistent handles. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ShutdownIsolate(void); +/* TODO(turnidge): Document behavior when there is no current isolate. */ + +/** + * Returns the current isolate. Will return NULL if there is no + * current isolate. + */ +DART_EXPORT Dart_Isolate Dart_CurrentIsolate(void); + +/** + * Returns the callback data associated with the current isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_CurrentIsolateData(void); + +/** + * Returns the callback data associated with the given isolate. This + * data was set when the isolate got created or initialized. + */ +DART_EXPORT void* Dart_IsolateData(Dart_Isolate isolate); + +/** + * Returns the current isolate group. Will return NULL if there is no + * current isolate group. + */ +DART_EXPORT Dart_IsolateGroup Dart_CurrentIsolateGroup(void); + +/** + * Returns the callback data associated with the current isolate group. This + * data was passed to the isolate group when it was created. + */ +DART_EXPORT void* Dart_CurrentIsolateGroupData(void); + +/** + * Gets an id that uniquely identifies current isolate group. + * + * It is the responsibility of the caller to free the returned ID. + */ +typedef int64_t Dart_IsolateGroupId; +DART_EXPORT Dart_IsolateGroupId Dart_CurrentIsolateGroupId(); + +/** + * Returns the callback data associated with the specified isolate group. This + * data was passed to the isolate when it was created. + * The embedder is responsible for ensuring the consistency of this data + * with respect to the lifecycle of an isolate group. + */ +DART_EXPORT void* Dart_IsolateGroupData(Dart_Isolate isolate); + +/** + * Returns the debugging name for the current isolate. + * + * This name is unique to each isolate and should only be used to make + * debugging messages more comprehensible. + */ +DART_EXPORT Dart_Handle Dart_DebugName(void); + +/** + * Returns the debugging name for the current isolate. + * + * This name is unique to each isolate and should only be used to make + * debugging messages more comprehensible. + * + * The returned string is scope allocated and is only valid until the next call + * to Dart_ExitScope. + */ +DART_EXPORT const char* Dart_DebugNameToCString(void); + +/** + * Returns the ID for an isolate which is used to query the service protocol. + * + * It is the responsibility of the caller to free the returned ID. + */ +DART_EXPORT const char* Dart_IsolateServiceId(Dart_Isolate isolate); + +/** + * Enters an isolate. After calling this function, + * the current isolate will be set to the provided isolate. + * + * Requires there to be no current isolate. Multiple threads may not be in + * the same isolate at once. + */ +DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate); + +/** + * Kills the given isolate. + * + * This function has the same effect as dart:isolate's + * Isolate.kill(priority:immediate). + * It can interrupt ordinary Dart code but not native code. If the isolate is + * in the middle of a long running native function, the isolate will not be + * killed until control returns to Dart. + * + * Does not require a current isolate. It is safe to kill the current isolate if + * there is one. + */ +DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate); + +/** + * Notifies the VM that the embedder expects to be idle until |deadline|. The VM + * may use this time to perform garbage collection or other tasks to avoid + * delays during execution of Dart code in the future. + * + * |deadline| is measured in microseconds against the system's monotonic time. + * This clock can be accessed via Dart_TimelineGetMicros(). + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_NotifyIdle(int64_t deadline); + +typedef void (*Dart_HeapSamplingReportCallback)(void* context, + intptr_t heap_size, + const char* cls_name, + void* data); + +typedef void* (*Dart_HeapSamplingCreateCallback)( + Dart_Isolate isolate, + Dart_IsolateGroup isolate_group); +typedef void (*Dart_HeapSamplingDeleteCallback)(void* data); + +/** + * Starts the heap sampling profiler for each thread in the VM. + */ +DART_EXPORT void Dart_EnableHeapSampling(); + +/* + * Stops the heap sampling profiler for each thread in the VM. + */ +DART_EXPORT void Dart_DisableHeapSampling(); + +/* Registers callbacks are invoked once per sampled allocation upon object + * allocation and garbage collection. + * + * |create_callback| can be used to associate additional data with the sampled + * allocation, such as a stack trace. This data pointer will be passed to + * |delete_callback| to allow for proper disposal when the object associated + * with the allocation sample is collected. + * + * The provided callbacks must not call into the VM and should do as little + * work as possible to avoid performance penalities during object allocation and + * garbage collection. + * + * NOTE: It is a fatal error to set either callback to null once they have been + * initialized. + */ +DART_EXPORT void Dart_RegisterHeapSamplingCallback( + Dart_HeapSamplingCreateCallback create_callback, + Dart_HeapSamplingDeleteCallback delete_callback); + +/* + * Reports the surviving allocation samples for all live isolate groups in the + * VM. + * + * When the callback is invoked: + * - |context| will be the context object provided when invoking + * |Dart_ReportSurvivingAllocations|. This can be safely set to null if not + * required. + * - |heap_size| will be equal to the size of the allocated object associated + * with the sample. + * - |cls_name| will be a C String representing + * the class name of the allocated object. This string is valid for the + * duration of the call to Dart_ReportSurvivingAllocations and can be + * freed by the VM at any point after the method returns. + * - |data| will be set to the data associated with the sample by + * |Dart_HeapSamplingCreateCallback|. + */ +DART_EXPORT void Dart_ReportSurvivingAllocations( + Dart_HeapSamplingReportCallback callback, + void* context); + +/* + * Sets the average heap sampling rate based on a number of |bytes| for each + * thread. + * + * In other words, approximately every |bytes| allocated will create a sample. + * Defaults to 512 KiB. + */ +DART_EXPORT void Dart_SetHeapSamplingPeriod(intptr_t bytes); + +/** + * Notifies the VM that the embedder expects the application's working set has + * recently shrunk significantly and is not expected to rise in the near future. + * The VM may spend O(heap-size) time performing clean up work. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_NotifyDestroyed(void); + +/** + * Notifies the VM that the system is running low on memory. + * + * Does not require a current isolate. Only valid after calling Dart_Initialize. + */ +DART_EXPORT void Dart_NotifyLowMemory(void); + +typedef enum { + /** + * Balanced + */ + Dart_PerformanceMode_Default, + /** + * Optimize for low latency, at the expense of throughput and memory overhead + * by performing work in smaller batches (requiring more overhead) or by + * delaying work (requiring more memory). An embedder should not remain in + * this mode indefinitely. + */ + Dart_PerformanceMode_Latency, + /** + * Optimize for high throughput, at the expense of latency and memory overhead + * by performing work in larger batches with more intervening growth. + */ + Dart_PerformanceMode_Throughput, + /** + * Optimize for low memory, at the expensive of throughput and latency by more + * frequently performing work. + */ + Dart_PerformanceMode_Memory, +} Dart_PerformanceMode; + +/** + * Set the desired performance trade-off. + * + * Requires a current isolate. + * + * Returns the previous performance mode. + */ +DART_EXPORT Dart_PerformanceMode +Dart_SetPerformanceMode(Dart_PerformanceMode mode); + +/** + * Starts the CPU sampling profiler. + */ +DART_EXPORT void Dart_StartProfiling(void); + +/** + * Stops the CPU sampling profiler. + * + * Note that some profile samples might still be taken after this function + * returns due to the asynchronous nature of the implementation on some + * platforms. + */ +DART_EXPORT void Dart_StopProfiling(void); + +/** + * Notifies the VM that the current thread should not be profiled until a + * matching call to Dart_ThreadEnableProfiling is made. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + * This function should be used when an embedder knows a thread is about + * to make a blocking call and wants to avoid unnecessary interrupts by + * the profiler. + */ +DART_EXPORT void Dart_ThreadDisableProfiling(void); + +/** + * Notifies the VM that the current thread should be profiled. + * + * NOTE: It is only legal to call this function *after* calling + * Dart_ThreadDisableProfiling. + * + * NOTE: By default, if a thread has entered an isolate it will be profiled. + */ +DART_EXPORT void Dart_ThreadEnableProfiling(void); + +/** + * Register symbol information for the Dart VM's profiler and crash dumps. + * + * This consumes the output of //topaz/runtime/dart/profiler_symbols, which + * should be treated as opaque. + */ +DART_EXPORT void Dart_AddSymbols(const char* dso_name, + void* buffer, + intptr_t buffer_size); + +/** + * Exits an isolate. After this call, Dart_CurrentIsolate will + * return NULL. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitIsolate(void); +/* TODO(turnidge): We don't want users of the api to be able to exit a + * "pure" dart isolate. Implement and document. */ + +/** + * Creates a full snapshot of the current isolate heap. + * + * A full snapshot is a compact representation of the dart vm isolate heap + * and dart isolate heap states. These snapshots are used to initialize + * the vm isolate on startup and fast initialization of an isolate. + * A Snapshot of the heap is created before any dart code has executed. + * + * Requires there to be a current isolate. Not available in the precompiled + * runtime (check Dart_IsPrecompiledRuntime). + * + * \param vm_snapshot_data_buffer Returns a pointer to a buffer containing the + * vm snapshot. This buffer is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param vm_snapshot_data_size Returns the size of vm_snapshot_data_buffer. + * \param isolate_snapshot_data_buffer Returns a pointer to a buffer containing + * the isolate snapshot. This buffer is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param isolate_snapshot_data_size Returns the size of + * isolate_snapshot_data_buffer. + * \param is_core Create a snapshot containing core libraries. + * Such snapshot should be agnostic to null safety mode. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateSnapshot(uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + bool is_core); + +/** + * Returns whether the buffer contains a kernel file. + * + * \param buffer Pointer to a buffer that might contain a kernel binary. + * \param buffer_size Size of the buffer. + * + * \return Whether the buffer contains a kernel binary (full or partial). + */ +DART_EXPORT bool Dart_IsKernel(const uint8_t* buffer, intptr_t buffer_size); + +/** + * Make isolate runnable. + * + * When isolates are spawned, this function is used to indicate that + * the creation and initialization (including script loading) of the + * isolate is complete and the isolate can start. + * This function expects there to be no current isolate. + * + * \param isolate The isolate to be made runnable. + * + * \return NULL if successful. Returns an error message otherwise. The caller + * is responsible for freeing the error message. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_IsolateMakeRunnable( + Dart_Isolate isolate); + +/* + * ================== + * Messages and Ports + * ================== + */ + +/** + * A port is used to send or receive inter-isolate messages + */ +typedef int64_t Dart_Port; + +/** + * ILLEGAL_PORT is a port number guaranteed never to be associated with a valid + * port. + */ +#define ILLEGAL_PORT ((Dart_Port)0) + +/** + * A message notification callback. + * + * This callback allows the embedder to provide a custom wakeup mechanism for + * the delivery of inter-isolate messages. This function is called once per + * message on an arbitrary thread. It is the responsibility of the embedder to + * eventually call Dart_HandleMessage once per callback received with the + * destination isolate set as the current isolate to process the message. + */ +typedef void (*Dart_MessageNotifyCallback)(Dart_Isolate destination_isolate); + +/** + * Allows embedders to provide a custom wakeup mechanism for the delivery of + * inter-isolate messages. This setting only applies to the current isolate. + * + * This mechanism is optional: if not provided, the isolate will be scheduled on + * a VM-managed thread pool. An embedder should provide this callback if it + * wants to run an isolate on a specific thread or to interleave handling of + * inter-isolate messages with other event sources. + * + * Most embedders will only call this function once, before isolate + * execution begins. If this function is called after isolate + * execution begins, the embedder is responsible for threading issues. + */ +DART_EXPORT void Dart_SetMessageNotifyCallback( + Dart_MessageNotifyCallback message_notify_callback); +/* TODO(turnidge): Consider moving this to isolate creation so that it + * is impossible to mess up. */ + +/** + * Query the current message notify callback for the isolate. + * + * \return The current message notify callback for the isolate. + */ +DART_EXPORT Dart_MessageNotifyCallback Dart_GetMessageNotifyCallback(void); + +/** + * The VM's default message handler supports pausing an isolate before it + * processes the first message and right after the it processes the isolate's + * final message. This can be controlled for all isolates by two VM flags: + * + * `--pause-isolates-on-start` + * `--pause-isolates-on-exit` + * + * Additionally, Dart_SetShouldPauseOnStart and Dart_SetShouldPauseOnExit can be + * used to control this behaviour on a per-isolate basis. + * + * When an embedder is using a Dart_MessageNotifyCallback the embedder + * needs to cooperate with the VM so that the service protocol can report + * accurate information about isolates and so that tools such as debuggers + * work reliably. + * + * The following functions can be used to implement pausing on start and exit. + */ + +/** + * If the VM flag `--pause-isolates-on-start` was passed this will be true. + * + * \return A boolean value indicating if pause on start was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnStart(void); + +/** + * Override the VM flag `--pause-isolates-on-start` for the current isolate. + * + * \param should_pause Should the isolate be paused on start? + * + * NOTE: This must be called before Dart_IsolateMakeRunnable. + */ +DART_EXPORT void Dart_SetShouldPauseOnStart(bool should_pause); + +/** + * Is the current isolate paused on start? + * + * \return A boolean value indicating if the isolate is paused on start. + */ +DART_EXPORT bool Dart_IsPausedOnStart(void); + +/** + * Called when the embedder has paused the current isolate on start and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on start? + */ +DART_EXPORT void Dart_SetPausedOnStart(bool paused); + +/** + * If the VM flag `--pause-isolates-on-exit` was passed this will be true. + * + * \return A boolean value indicating if pause on exit was requested. + */ +DART_EXPORT bool Dart_ShouldPauseOnExit(void); + +/** + * Override the VM flag `--pause-isolates-on-exit` for the current isolate. + * + * \param should_pause Should the isolate be paused on exit? + * + */ +DART_EXPORT void Dart_SetShouldPauseOnExit(bool should_pause); + +/** + * Is the current isolate paused on exit? + * + * \return A boolean value indicating if the isolate is paused on exit. + */ +DART_EXPORT bool Dart_IsPausedOnExit(void); + +/** + * Called when the embedder has paused the current isolate on exit and when + * the embedder has resumed the isolate. + * + * \param paused Is the isolate paused on exit? + */ +DART_EXPORT void Dart_SetPausedOnExit(bool paused); + +/** + * Called when the embedder has caught a top level unhandled exception error + * in the current isolate. + * + * NOTE: It is illegal to call this twice on the same isolate without first + * clearing the sticky error to null. + * + * \param error The unhandled exception error. + */ +DART_EXPORT void Dart_SetStickyError(Dart_Handle error); + +/** + * Does the current isolate have a sticky error? + */ +DART_EXPORT bool Dart_HasStickyError(void); + +/** + * Gets the sticky error for the current isolate. + * + * \return A handle to the sticky error object or null. + */ +DART_EXPORT Dart_Handle Dart_GetStickyError(void); + +/** + * Handles the next pending message for the current isolate. + * + * May generate an unhandled exception error. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_HandleMessage(void); + +/** + * Drains the microtask queue, then blocks the calling thread until the current + * isolate receives a message, then handles all messages. + * + * \param timeout_millis When non-zero, the call returns after the indicated + number of milliseconds even if no message was received. + * \return A valid handle if no error occurs, otherwise an error handle. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_WaitForEvent(int64_t timeout_millis); + +/** + * Handles any pending messages for the vm service for the current + * isolate. + * + * This function may be used by an embedder at a breakpoint to avoid + * pausing the vm service. + * + * This function can indirectly cause the message notify callback to + * be called. + * + * \return true if the vm service requests the program resume + * execution, false otherwise + */ +DART_EXPORT bool Dart_HandleServiceMessages(void); + +/** + * Does the current isolate have pending service messages? + * + * \return true if the isolate has pending service messages, false otherwise. + */ +DART_EXPORT bool Dart_HasServiceMessages(void); + +/** + * Processes any incoming messages for the current isolate. + * + * This function may only be used when the embedder has not provided + * an alternate message delivery mechanism with + * Dart_SetMessageCallbacks. It is provided for convenience. + * + * This function waits for incoming messages for the current + * isolate. As new messages arrive, they are handled using + * Dart_HandleMessage. The routine exits when all ports to the + * current isolate are closed. + * + * \return A valid handle if the run loop exited successfully. If an + * exception or other error occurs while processing messages, an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_RunLoop(void); + +/** + * Lets the VM run message processing for the isolate. + * + * This function expects there to a current isolate and the current isolate + * must not have an active api scope. The VM will take care of making the + * isolate runnable (if not already), handles its message loop and will take + * care of shutting the isolate down once it's done. + * + * \param errors_are_fatal Whether uncaught errors should be fatal. + * \param on_error_port A port to notify on uncaught errors (or ILLEGAL_PORT). + * \param on_exit_port A port to notify on exit (or ILLEGAL_PORT). + * \param error A non-NULL pointer which will hold an error message if the call + * fails. The error has to be free()ed by the caller. + * + * \return If successful the VM takes ownership of the isolate and takes care + * of its message loop. If not successful the caller retains ownership of the + * isolate. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT bool Dart_RunLoopAsync( + bool errors_are_fatal, + Dart_Port on_error_port, + Dart_Port on_exit_port, + char** error); + +/* TODO(turnidge): Should this be removed from the public api? */ + +/** + * Gets the main port id for the current isolate. + */ +DART_EXPORT Dart_Port Dart_GetMainPortId(void); + +/** + * Does the current isolate have live ReceivePorts? + * + * A ReceivePort is live when it has not been closed. + */ +DART_EXPORT bool Dart_HasLivePorts(void); + +/** + * Posts a message for some isolate. The message is a serialized + * object. + * + * Requires there to be a current isolate. + * + * For posting messages outside of an isolate see \ref Dart_PostCObject. + * + * \param port_id The destination port. + * \param object An object from the current isolate. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_Post(Dart_Port port_id, Dart_Handle object); + +/** + * Returns a new SendPort with the provided port id. + * + * \param port_id The destination port. + * + * \return A new SendPort if no errors occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewSendPort(Dart_Port port_id); + +/** + * Gets the SendPort id for the provided SendPort. + * \param port A SendPort object whose id is desired. + * \param port_id Returns the id of the SendPort. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port, + Dart_Port* port_id); + +/* + * ====== + * Scopes + * ====== + */ + +/** + * Enters a new scope. + * + * All new local handles will be created in this scope. Additionally, + * some functions may return "scope allocated" memory which is only + * valid within this scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_EnterScope(void); + +/** + * Exits a scope. + * + * The previous scope (if any) becomes the current scope. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_ExitScope(void); + +/** + * The Dart VM uses "zone allocation" for temporary structures. Zones + * support very fast allocation of small chunks of memory. The chunks + * cannot be deallocated individually, but instead zones support + * deallocating all chunks in one fast operation. + * + * This function makes it possible for the embedder to allocate + * temporary data in the VMs zone allocator. + * + * Zone allocation is possible: + * 1. when inside a scope where local handles can be allocated + * 2. when processing a message from a native port in a native port + * handler + * + * All the memory allocated this way will be reclaimed either on the + * next call to Dart_ExitScope or when the native port handler exits. + * + * \param size Size of the memory to allocate. + * + * \return A pointer to the allocated memory. NULL if allocation + * failed. Failure might due to is no current VM zone. + */ +DART_EXPORT uint8_t* Dart_ScopeAllocate(intptr_t size); + +/* + * ======= + * Objects + * ======= + */ + +/** + * Returns the null object. + * + * \return A handle to the null object. + */ +DART_EXPORT Dart_Handle Dart_Null(void); + +/** + * Is this object null? + */ +DART_EXPORT bool Dart_IsNull(Dart_Handle object); + +/** + * Returns the empty string object. + * + * \return A handle to the empty string object. + */ +DART_EXPORT Dart_Handle Dart_EmptyString(void); + +/** + * Returns types that are not classes, and which therefore cannot be looked up + * as library members by Dart_GetType. + * + * \return A handle to the dynamic, void or Never type. + */ +DART_EXPORT Dart_Handle Dart_TypeDynamic(void); +DART_EXPORT Dart_Handle Dart_TypeVoid(void); +DART_EXPORT Dart_Handle Dart_TypeNever(void); + +/** + * Checks if the two objects are equal. + * + * The result of the comparison is returned through the 'equal' + * parameter. The return value itself is used to indicate success or + * failure, not equality. + * + * May generate an unhandled exception error. + * + * \param obj1 An object to be compared. + * \param obj2 An object to be compared. + * \param equal Returns the result of the equality comparison. + * + * \return A valid handle if no error occurs during the comparison. + */ +DART_EXPORT Dart_Handle Dart_ObjectEquals(Dart_Handle obj1, + Dart_Handle obj2, + bool* equal); + +/** + * Is this object an instance of some type? + * + * The result of the test is returned through the 'instanceof' parameter. + * The return value itself is used to indicate success or failure. + * + * \param object An object. + * \param type A type. + * \param instanceof Return true if 'object' is an instance of type 'type'. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ObjectIsType(Dart_Handle object, + Dart_Handle type, + bool* instanceof); + +/** + * Query object type. + * + * \param object Some Object. + * + * \return true if Object is of the specified type. + */ +DART_EXPORT bool Dart_IsInstance(Dart_Handle object); +DART_EXPORT bool Dart_IsNumber(Dart_Handle object); +DART_EXPORT bool Dart_IsInteger(Dart_Handle object); +DART_EXPORT bool Dart_IsDouble(Dart_Handle object); +DART_EXPORT bool Dart_IsBoolean(Dart_Handle object); +DART_EXPORT bool Dart_IsString(Dart_Handle object); +DART_EXPORT bool Dart_IsStringLatin1(Dart_Handle object); /* (ISO-8859-1) */ +DART_EXPORT bool Dart_IsExternalString(Dart_Handle object); +DART_EXPORT bool Dart_IsList(Dart_Handle object); +DART_EXPORT bool Dart_IsMap(Dart_Handle object); +DART_EXPORT bool Dart_IsLibrary(Dart_Handle object); +DART_EXPORT bool Dart_IsType(Dart_Handle handle); +DART_EXPORT bool Dart_IsFunction(Dart_Handle handle); +DART_EXPORT bool Dart_IsVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsTypeVariable(Dart_Handle handle); +DART_EXPORT bool Dart_IsClosure(Dart_Handle object); +DART_EXPORT bool Dart_IsTypedData(Dart_Handle object); +DART_EXPORT bool Dart_IsByteBuffer(Dart_Handle object); +DART_EXPORT bool Dart_IsFuture(Dart_Handle object); + +/* + * ========= + * Instances + * ========= + */ + +/* + * For the purposes of the embedding api, not all objects returned are + * Dart language objects. Within the api, we use the term 'Instance' + * to indicate handles which refer to true Dart language objects. + * + * TODO(turnidge): Reorganize the "Object" section above, pulling down + * any functions that more properly belong here. */ + +/** + * Gets the type of a Dart language object. + * + * \param instance Some Dart object. + * + * \return If no error occurs, the type is returned. Otherwise an + * error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_InstanceGetType(Dart_Handle instance); + +/** + * Returns the name for the provided class type. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_ClassName(Dart_Handle cls_type); + +/** + * Returns the name for the provided function or method. + * + * \return A valid string handle if no error occurs during the + * operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionName(Dart_Handle function); + +/** + * Returns a handle to the owner of a function. + * + * The owner of an instance method or a static method is its defining + * class. The owner of a top-level function is its defining + * library. The owner of the function of a non-implicit closure is the + * function of the method or closure that defines the non-implicit + * closure. + * + * \return A valid handle to the owner of the function, or an error + * handle if the argument is not a valid handle to a function. + */ +DART_EXPORT Dart_Handle Dart_FunctionOwner(Dart_Handle function); + +/** + * Determines whether a function handle refers to a static function + * of method. + * + * For the purposes of the embedding API, a top-level function is + * implicitly declared static. + * + * \param function A handle to a function or method declaration. + * \param is_static Returns whether the function or method is declared static. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_FunctionIsStatic(Dart_Handle function, + bool* is_static); + +/** + * Is this object a closure resulting from a tear-off (closurized method)? + * + * Returns true for closures produced when an ordinary method is accessed + * through a getter call. Returns false otherwise, in particular for closures + * produced from local function declarations. + * + * \param object Some Object. + * + * \return true if Object is a tear-off. + */ +DART_EXPORT bool Dart_IsTearOff(Dart_Handle object); + +/** + * Retrieves the function of a closure. + * + * \return A handle to the function of the closure, or an error handle if the + * argument is not a closure. + */ +DART_EXPORT Dart_Handle Dart_ClosureFunction(Dart_Handle closure); + +/** + * Returns a handle to the library which contains class. + * + * \return A valid handle to the library with owns class, null if the class + * has no library or an error handle if the argument is not a valid handle + * to a class type. + */ +DART_EXPORT Dart_Handle Dart_ClassLibrary(Dart_Handle cls_type); + +/* + * ============================= + * Numbers, Integers and Doubles + * ============================= + */ + +/** + * Does this Integer fit into a 64-bit signed integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit signed integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoInt64(Dart_Handle integer, + bool* fits); + +/** + * Does this Integer fit into a 64-bit unsigned integer? + * + * \param integer An integer. + * \param fits Returns true if the integer fits into a 64-bit unsigned integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerFitsIntoUint64(Dart_Handle integer, + bool* fits); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewInteger(int64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The unsigned value of the integer. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromUint64(uint64_t value); + +/** + * Returns an Integer with the provided value. + * + * \param value The value of the integer represented as a C string + * containing a hexadecimal number. + * + * \return The Integer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewIntegerFromHexCString(const char* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit signed integer, otherwise an error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToInt64(Dart_Handle integer, + int64_t* value); + +/** + * Gets the value of an Integer. + * + * The integer must fit into a 64-bit unsigned integer, otherwise an + * error occurs. + * + * \param integer An Integer. + * \param value Returns the value of the Integer. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToUint64(Dart_Handle integer, + uint64_t* value); + +/** + * Gets the value of an integer as a hexadecimal C string. + * + * \param integer An Integer. + * \param value Returns the value of the Integer as a hexadecimal C + * string. This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_IntegerToHexCString(Dart_Handle integer, + const char** value); + +/** + * Returns a Double with the provided value. + * + * \param value A double. + * + * \return The Double object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewDouble(double value); + +/** + * Gets the value of a Double + * + * \param double_obj A Double + * \param value Returns the value of the Double. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_DoubleValue(Dart_Handle double_obj, double* value); + +/** + * Returns a closure of static function 'function_name' in the class 'class_name' + * in the exported namespace of specified 'library'. + * + * \param library Library object + * \param cls_type Type object representing a Class + * \param function_name Name of the static function in the class + * + * \return A valid Dart instance if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_GetStaticMethodClosure(Dart_Handle library, + Dart_Handle cls_type, + Dart_Handle function_name); + +/* + * ======== + * Booleans + * ======== + */ + +/** + * Returns the True object. + * + * Requires there to be a current isolate. + * + * \return A handle to the True object. + */ +DART_EXPORT Dart_Handle Dart_True(void); + +/** + * Returns the False object. + * + * Requires there to be a current isolate. + * + * \return A handle to the False object. + */ +DART_EXPORT Dart_Handle Dart_False(void); + +/** + * Returns a Boolean with the provided value. + * + * \param value true or false. + * + * \return The Boolean object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewBoolean(bool value); + +/** + * Gets the value of a Boolean + * + * \param boolean_obj A Boolean + * \param value Returns the value of the Boolean. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_BooleanValue(Dart_Handle boolean_obj, bool* value); + +/* + * ======= + * Strings + * ======= + */ + +/** + * Gets the length of a String. + * + * \param str A String. + * \param length Returns the length of the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringLength(Dart_Handle str, intptr_t* length); + +/** + * Returns a String built from the provided C string + * (There is an implicit assumption that the C string passed in contains + * UTF-8 encoded characters and '\0' is considered as a termination + * character). + * + * \param str A C String + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromCString(const char* str); +/* TODO(turnidge): Document what happens when we run out of memory + * during this call. */ + +/** + * Returns a String built from an array of UTF-8 encoded characters. + * + * \param utf8_array An array of UTF-8 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF8(const uint8_t* utf8_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-16 encoded characters. + * + * \param utf16_array An array of UTF-16 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF16(const uint16_t* utf16_array, + intptr_t length); + +/** + * Returns a String built from an array of UTF-32 encoded characters. + * + * \param utf32_array An array of UTF-32 encoded characters. + * \param length The length of the codepoints array. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewStringFromUTF32(const int32_t* utf32_array, + intptr_t length); + +/** + * Returns a String which references an external array of + * Latin-1 (ISO-8859-1) encoded characters. + * + * \param latin1_array Array of Latin-1 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalLatin1String(const uint8_t* latin1_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a String which references an external array of UTF-16 encoded + * characters. + * + * \param utf16_array An array of UTF-16 encoded characters. This must not move. + * \param length The length of the characters array. + * \param peer An external pointer to associate with this string. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A callback to be called when this string is finalized. + * + * \return The String object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalUTF16String(const uint16_t* utf16_array, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Gets the C string representation of a String. + * (It is a sequence of UTF-8 encoded values with a '\0' termination.) + * + * \param str A string. + * \param cstr Returns the String represented as a C string. + * This C string is scope allocated and is only valid until + * the next call to Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToCString(Dart_Handle str, + const char** cstr); + +/** + * Gets a UTF-8 encoded representation of a String. + * + * Any unpaired surrogate code points in the string will be converted as + * replacement characters (U+FFFD, 0xEF 0xBF 0xBD in UTF-8). If you need + * to preserve unpaired surrogates, use the Dart_StringToUTF16 function. + * + * \param str A string. + * \param utf8_array Returns the String represented as UTF-8 code + * units. This UTF-8 array is scope allocated and is only valid + * until the next call to Dart_ExitScope. + * \param length Used to return the length of the array which was + * actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF8(Dart_Handle str, + uint8_t** utf8_array, + intptr_t* length); + +/** + * Gets the data corresponding to the string object. This function returns + * the data only for Latin-1 (ISO-8859-1) string objects. For all other + * string objects it returns an error. + * + * \param str A string. + * \param latin1_array An array allocated by the caller, used to return + * the string data. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToLatin1(Dart_Handle str, + uint8_t* latin1_array, + intptr_t* length); + +/** + * Gets the UTF-16 encoded representation of a string. + * + * \param str A string. + * \param utf16_array An array allocated by the caller, used to return + * the array of UTF-16 encoded characters. + * \param length Used to pass in the length of the provided array. + * Used to return the length of the array which was actually used. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringToUTF16(Dart_Handle str, + uint16_t* utf16_array, + intptr_t* length); + +/** + * Gets the storage size in bytes of a String. + * + * \param str A String. + * \param size Returns the storage size in bytes of the String. + * This is the size in bytes needed to store the String. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_StringStorageSize(Dart_Handle str, intptr_t* size); + +/** + * Retrieves some properties associated with a String. + * Properties retrieved are: + * - character size of the string (one or two byte) + * - length of the string + * - peer pointer of string if it is an external string. + * \param str A String. + * \param char_size Returns the character size of the String. + * \param str_len Returns the length of the String. + * \param peer Returns the peer pointer associated with the String or 0 if + * there is no peer pointer for it. + * \return Success if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_StringGetProperties(Dart_Handle str, + intptr_t* char_size, + intptr_t* str_len, + void** peer); + +/* + * ===== + * Lists + * ===== + */ + +/** + * Returns a List of the desired length. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewList(intptr_t length); + +typedef enum { + Dart_CoreType_Dynamic, + Dart_CoreType_Int, + Dart_CoreType_String, +} Dart_CoreType_Id; + +// TODO(bkonyi): convert this to use nullable types once NNBD is enabled. +/** + * Returns a List of the desired length with the desired legacy element type. + * + * \param element_type_id The type of elements of the list. + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns an error + * handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOf(Dart_CoreType_Id element_type_id, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type. + * + * \param element_type Handle to a nullable type object. E.g., from + * Dart_GetType or Dart_GetNullableType. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfType(Dart_Handle element_type, + intptr_t length); + +/** + * Returns a List of the desired length with the desired element type, filled + * with the provided object. + * + * \param element_type Handle to a type object. E.g., from Dart_GetType. + * + * \param fill_object Handle to an object of type 'element_type' that will be + * used to populate the list. This parameter can only be Dart_Null() if the + * length of the list is 0 or 'element_type' is a nullable type. + * + * \param length The length of the list. + * + * \return The List object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewListOfTypeFilled(Dart_Handle element_type, + Dart_Handle fill_object, + intptr_t length); + +/** + * Gets the length of a List. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param length Returns the length of the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListLength(Dart_Handle list, intptr_t* length); + +/** + * Gets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param index A valid index into the List. + * + * \return The Object in the List at the specified index if no error + * occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_ListGetAt(Dart_Handle list, intptr_t index); + +/** +* Gets a range of Objects from a List. +* +* If any of the requested index values are out of bounds, an error occurs. +* +* May generate an unhandled exception error. +* +* \param list A List. +* \param offset The offset of the first item to get. +* \param length The number of items to get. +* \param result A pointer to fill with the objects. +* +* \return Success if no error occurs during the operation. +*/ +DART_EXPORT Dart_Handle Dart_ListGetRange(Dart_Handle list, + intptr_t offset, + intptr_t length, + Dart_Handle* result); + +/** + * Sets the Object at some index of a List. + * + * If the index is out of bounds, an error occurs. + * + * May generate an unhandled exception error. + * + * \param list A List. + * \param index A valid index into the List. + * \param value The Object to put in the List. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT Dart_Handle Dart_ListSetAt(Dart_Handle list, + intptr_t index, + Dart_Handle value); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListGetAsBytes(Dart_Handle list, + intptr_t offset, + uint8_t* native_array, + intptr_t length); + +/** + * May generate an unhandled exception error. + */ +DART_EXPORT Dart_Handle Dart_ListSetAsBytes(Dart_Handle list, + intptr_t offset, + const uint8_t* native_array, + intptr_t length); + +/* + * ==== + * Maps + * ==== + */ + +/** + * Gets the Object at some key of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * \param key An Object. + * + * \return The value in the map at the specified key, null if the map does not + * contain the key, or an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapGetAt(Dart_Handle map, Dart_Handle key); + +/** + * Returns whether the Map contains a given key. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return A handle on a boolean indicating whether map contains the key. + * Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_MapContainsKey(Dart_Handle map, Dart_Handle key); + +/** + * Gets the list of keys of a Map. + * + * May generate an unhandled exception error. + * + * \param map A Map. + * + * \return The list of key Objects if no error occurs. Otherwise returns an + * error handle. + */ +DART_EXPORT Dart_Handle Dart_MapKeys(Dart_Handle map); + +/* + * ========== + * Typed Data + * ========== + */ + +typedef enum { + Dart_TypedData_kByteData = 0, + Dart_TypedData_kInt8, + Dart_TypedData_kUint8, + Dart_TypedData_kUint8Clamped, + Dart_TypedData_kInt16, + Dart_TypedData_kUint16, + Dart_TypedData_kInt32, + Dart_TypedData_kUint32, + Dart_TypedData_kInt64, + Dart_TypedData_kUint64, + Dart_TypedData_kFloat32, + Dart_TypedData_kFloat64, + Dart_TypedData_kInt32x4, + Dart_TypedData_kFloat32x4, + Dart_TypedData_kFloat64x2, + Dart_TypedData_kInvalid +} Dart_TypedData_Type; + +/** + * Return type if this object is a TypedData object. + * + * \return kInvalid if the object is not a TypedData object or the appropriate + * Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type Dart_GetTypeOfTypedData(Dart_Handle object); + +/** + * Return type if this object is an external TypedData object. + * + * \return kInvalid if the object is not an external TypedData object or + * the appropriate Dart_TypedData_Type. + */ +DART_EXPORT Dart_TypedData_Type +Dart_GetTypeOfExternalTypedData(Dart_Handle object); + +/** + * Returns a TypedData object of the desired length and type. + * + * \param type The type of the TypedData object. + * \param length The length of the TypedData object (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewTypedData(Dart_TypedData_Type type, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewExternalTypedData(Dart_TypedData_Type type, + void* data, + intptr_t length); + +/** + * Returns a TypedData object which references an external data array. + * + * \param type The type of the data array. + * \param data A data array. This array must not move. + * \param length The length of the data array (length in type units). + * \param peer A pointer to a native object or NULL. This value is + * provided to callback when it is invoked. + * \param external_allocation_size The number of externally allocated + * bytes for peer. Used to inform the garbage collector. + * \param callback A function pointer that will be invoked sometime + * after the object is garbage collected, unless the handle has been deleted. + * A valid callback needs to be specified it cannot be NULL. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle +Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type, + void* data, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); +DART_EXPORT Dart_Handle Dart_NewUnmodifiableExternalTypedDataWithFinalizer( + Dart_TypedData_Type type, + const void* data, + intptr_t length, + void* peer, + intptr_t external_allocation_size, + Dart_HandleFinalizer callback); + +/** + * Returns a ByteBuffer object for the typed data. + * + * \param typed_data The TypedData object. + * + * \return The ByteBuffer object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewByteBuffer(Dart_Handle typed_data); + +/** + * Acquires access to the internal data address of a TypedData object. + * + * \param object The typed data object whose internal data address is to + * be accessed. + * \param type The type of the object is returned here. + * \param data The internal data address is returned here. + * \param len Size of the typed array is returned here. + * + * Notes: + * When the internal address of the object is acquired any calls to a + * Dart API function that could potentially allocate an object or run + * any Dart code will return an error. + * + * Any Dart API functions for accessing the data should not be called + * before the corresponding release. In particular, the object should + * not be acquired again before its release. This leads to undefined + * behavior. + * + * \return Success if the internal data address is acquired successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataAcquireData(Dart_Handle object, + Dart_TypedData_Type* type, + void** data, + intptr_t* len); + +/** + * Releases access to the internal data address that was acquired earlier using + * Dart_TypedDataAcquireData. + * + * \param object The typed data object whose internal data address is to be + * released. + * + * \return Success if the internal data address is released successfully. + * Otherwise, returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_TypedDataReleaseData(Dart_Handle object); + +/** + * Returns the TypedData object associated with the ByteBuffer object. + * + * \param byte_buffer The ByteBuffer object. + * + * \return The TypedData object if no error occurs. Otherwise returns + * an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetDataFromByteBuffer(Dart_Handle byte_buffer); + +/* + * ============================================================ + * Invoking Constructors, Methods, Closures and Field accessors + * ============================================================ + */ + +/** + * Invokes a constructor, creating a new object. + * + * This function allows hidden constructors (constructors with leading + * underscores) to be called. + * + * \param type Type of object to be constructed. + * \param constructor_name The name of the constructor to invoke. Use + * Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * This name should not include the name of the class. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the constructor. + * + * \return If the constructor is called and completes successfully, + * then the new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_New(Dart_Handle type, + Dart_Handle constructor_name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Allocate a new object without invoking a constructor. + * + * \param type The type of an object to be allocated. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_Allocate(Dart_Handle type); + +/** + * Allocate a new object without invoking a constructor, and sets specified + * native fields. + * + * \param type The type of an object to be allocated. + * \param num_native_fields The number of native fields to set. + * \param native_fields An array containing the value of native fields. + * + * \return The new object. If an error occurs during execution, then an + * error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_AllocateWithNativeFields(Dart_Handle type, + intptr_t num_native_fields, + const intptr_t* native_fields); + +/** + * Invokes a method or function. + * + * The 'target' parameter may be an object, type, or library. If + * 'target' is an object, then this function will invoke an instance + * method. If 'target' is a type, then this function will invoke a + * static method. If 'target' is a library, then this function will + * invoke a top-level function from that library. + * NOTE: This API call cannot be used to invoke methods of a type object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param target An object, type, or library. + * \param name The name of the function or method to invoke. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the function or method is called and completes + * successfully, then the return value is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_Invoke(Dart_Handle target, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); +/* TODO(turnidge): Document how to invoke operators. */ + +/** + * Invokes a Closure with the given arguments. + * + * May generate an unhandled exception error. + * + * \return If no error occurs during execution, then the result of + * invoking the closure is returned. If an error occurs during + * execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeClosure(Dart_Handle closure, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Invokes a Generative Constructor on an object that was previously + * allocated using Dart_Allocate/Dart_AllocateWithNativeFields. + * + * The 'object' parameter must be an object. + * + * This function ignores visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param object An object. + * \param name The name of the constructor to invoke. + * Use Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor. + * \param number_of_arguments Size of the arguments array. + * \param arguments An array of arguments to the function. + * + * \return If the constructor is called and completes + * successfully, then the object is returned. If an error + * occurs during execution, then an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_InvokeConstructor(Dart_Handle object, + Dart_Handle name, + int number_of_arguments, + Dart_Handle* arguments); + +/** + * Gets the value of a field. + * + * The 'container' parameter may be an object, type, or library. If + * 'container' is an object, then this function will access an + * instance field. If 'container' is a type, then this function will + * access a static field. If 'container' is a library, then this + * function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * + * \return If no error occurs, then the value of the field is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetField(Dart_Handle container, Dart_Handle name); + +/** + * Sets the value of a field. + * + * The 'container' parameter may actually be an object, type, or + * library. If 'container' is an object, then this function will + * access an instance field. If 'container' is a type, then this + * function will access a static field. If 'container' is a library, + * then this function will access a top-level variable. + * NOTE: This API call cannot be used to access fields of a type object. + * + * This function ignores field visibility (leading underscores in names). + * + * May generate an unhandled exception error. + * + * \param container An object, type, or library. + * \param name A field name. + * \param value The new field value. + * + * \return A valid handle if no error occurs. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_SetField(Dart_Handle container, Dart_Handle name, Dart_Handle value); + +/* + * ========== + * Exceptions + * ========== + */ + +/* + * TODO(turnidge): Remove these functions from the api and replace all + * uses with Dart_NewUnhandledExceptionError. */ + +/** + * Throws an exception. + * + * This function causes a Dart language exception to be thrown. This + * will proceed in the standard way, walking up Dart frames until an + * appropriate 'catch' block is found, executing 'finally' blocks, + * etc. + * + * If an error handle is passed into this function, the error is + * propagated immediately. See Dart_PropagateError for a discussion + * of error propagation. + * + * If successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ThrowException(Dart_Handle exception); + +/** + * Rethrows an exception. + * + * Rethrows an exception, unwinding all dart frames on the stack. If + * successful, this function does not return. Note that this means + * that the destructors of any stack-allocated C++ objects will not be + * called. If there are no Dart frames on the stack, an error occurs. + * + * \return An error handle if the exception was not thrown. + * Otherwise the function does not return. + */ +DART_EXPORT Dart_Handle Dart_ReThrowException(Dart_Handle exception, + Dart_Handle stacktrace); + +/* + * =========================== + * Native fields and functions + * =========================== + */ + +/** + * Gets the number of native instance fields in an object. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceFieldCount(Dart_Handle obj, + int* count); + +/** + * Gets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_GetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t* value); + +/** + * Sets the value of a native field. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle Dart_SetNativeInstanceField(Dart_Handle obj, + int index, + intptr_t value); + +/** + * The arguments to a native function. + * + * This object is passed to a native function to represent its + * arguments and return value. It allows access to the arguments to a + * native function by index. It also allows the return value of a + * native function to be set. + */ +typedef struct _Dart_NativeArguments* Dart_NativeArguments; + +/** + * Extracts current isolate group data from the native arguments structure. + */ +DART_EXPORT void* Dart_GetNativeIsolateGroupData(Dart_NativeArguments args); + +typedef enum { + Dart_NativeArgument_kBool = 0, + Dart_NativeArgument_kInt32, + Dart_NativeArgument_kUint32, + Dart_NativeArgument_kInt64, + Dart_NativeArgument_kUint64, + Dart_NativeArgument_kDouble, + Dart_NativeArgument_kString, + Dart_NativeArgument_kInstance, + Dart_NativeArgument_kNativeFields, +} Dart_NativeArgument_Type; + +typedef struct _Dart_NativeArgument_Descriptor { + uint8_t type; + uint8_t index; +} Dart_NativeArgument_Descriptor; + +typedef union _Dart_NativeArgument_Value { + bool as_bool; + int32_t as_int32; + uint32_t as_uint32; + int64_t as_int64; + uint64_t as_uint64; + double as_double; + struct { + Dart_Handle dart_str; + void* peer; + } as_string; + struct { + intptr_t num_fields; + intptr_t* values; + } as_native_fields; + Dart_Handle as_instance; +} Dart_NativeArgument_Value; + +enum { + kNativeArgNumberPos = 0, + kNativeArgNumberSize = 8, + kNativeArgTypePos = kNativeArgNumberPos + kNativeArgNumberSize, + kNativeArgTypeSize = 8, +}; + +#define BITMASK(size) ((1 << size) - 1) +#define DART_NATIVE_ARG_DESCRIPTOR(type, position) \ + (((type & BITMASK(kNativeArgTypeSize)) << kNativeArgTypePos) | \ + (position & BITMASK(kNativeArgNumberSize))) + +/** + * Gets the native arguments based on the types passed in and populates + * the passed arguments buffer with appropriate native values. + * + * \param args the Native arguments block passed into the native call. + * \param num_arguments length of argument descriptor array and argument + * values array passed in. + * \param arg_descriptors an array that describes the arguments that + * need to be retrieved. For each argument to be retrieved the descriptor + * contains the argument number (0, 1 etc.) and the argument type + * described using Dart_NativeArgument_Type, e.g: + * DART_NATIVE_ARG_DESCRIPTOR(Dart_NativeArgument_kBool, 1) indicates + * that the first argument is to be retrieved and it should be a boolean. + * \param arg_values array into which the native arguments need to be + * extracted into, the array is allocated by the caller (it could be + * stack allocated to avoid the malloc/free performance overhead). + * + * \return Success if all the arguments could be extracted correctly, + * returns an error handle if there were any errors while extracting the + * arguments (mismatched number of arguments, incorrect types, etc.). + */ +DART_EXPORT Dart_Handle +Dart_GetNativeArguments(Dart_NativeArguments args, + int num_arguments, + const Dart_NativeArgument_Descriptor* arg_descriptors, + Dart_NativeArgument_Value* arg_values); + +/** + * Gets the native argument at some index. + */ +DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args, + int index); +/* TODO(turnidge): Specify the behavior of an out-of-bounds access. */ + +/** + * Gets the number of native arguments. + */ +DART_EXPORT int Dart_GetNativeArgumentCount(Dart_NativeArguments args); + +/** + * Gets all the native fields of the native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param num_fields size of the intptr_t array 'field_values' passed in. + * \param field_values intptr_t array in which native field values are returned. + * \return Success if the native fields where copied in successfully. Otherwise + * returns an error handle. On success the native field values are copied + * into the 'field_values' array, if the argument at 'arg_index' is a + * null object then 0 is copied as the native field values into the + * 'field_values' array. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeFieldsOfArgument(Dart_NativeArguments args, + int arg_index, + int num_fields, + intptr_t* field_values); + +/** + * Gets the native field of the receiver. + */ +DART_EXPORT Dart_Handle Dart_GetNativeReceiver(Dart_NativeArguments args, + intptr_t* value); + +/** + * Gets a string native argument at some index. + * \param args Native arguments structure. + * \param arg_index Index of the desired argument in the structure above. + * \param peer Returns the peer pointer if the string argument has one. + * \return Success if the string argument has a peer, if it does not + * have a peer then the String object is returned. Otherwise returns + * an error handle (argument is not a String object). + */ +DART_EXPORT Dart_Handle Dart_GetNativeStringArgument(Dart_NativeArguments args, + int arg_index, + void** peer); + +/** + * Gets an integer native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the integer value if the argument is an Integer. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args, + int index, + int64_t* value); + +/** + * Gets a boolean native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the boolean value if the argument is a Boolean. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args, + int index, + bool* value); + +/** + * Gets a double native argument at some index. + * \param args Native arguments structure. + * \param index Index of the desired argument in the structure above. + * \param value Returns the double value if the argument is a double. + * \return Success if no error occurs. Otherwise returns an error handle. + */ +DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args, + int index, + double* value); + +/** + * Sets the return value for a native function. + * + * If retval is an Error handle, then error will be propagated once + * the native functions exits. See Dart_PropagateError for a + * discussion of how different types of errors are propagated. + */ +DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args, + Dart_Handle retval); + +DART_EXPORT void Dart_SetWeakHandleReturnValue(Dart_NativeArguments args, + Dart_WeakPersistentHandle rval); + +DART_EXPORT void Dart_SetBooleanReturnValue(Dart_NativeArguments args, + bool retval); + +DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args, + int64_t retval); + +DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args, + double retval); + +/** + * A native function. + */ +typedef void (*Dart_NativeFunction)(Dart_NativeArguments arguments); + +/** + * Native entry resolution callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a native entry resolver. This callback is used to map a + * name/arity to a Dart_NativeFunction. If no function is found, the + * callback should return NULL. + * + * The parameters to the native resolver function are: + * \param name a Dart string which is the name of the native function. + * \param num_of_arguments is the number of arguments expected by the + * native function. + * \param auto_setup_scope is a boolean flag that can be set by the resolver + * to indicate if this function needs a Dart API scope (see Dart_EnterScope/ + * Dart_ExitScope) to be setup automatically by the VM before calling into + * the native function. By default most native functions would require this + * to be true but some light weight native functions which do not call back + * into the VM through the Dart API may not require a Dart scope to be + * setup automatically. + * + * \return A valid Dart_NativeFunction which resolves to a native entry point + * for the native function. + * + * See Dart_SetNativeResolver. + */ +typedef Dart_NativeFunction (*Dart_NativeEntryResolver)(Dart_Handle name, + int num_of_arguments, + bool* auto_setup_scope); +/* TODO(turnidge): Consider renaming to NativeFunctionResolver or + * NativeResolver. */ + +/** + * Native entry symbol lookup callback. + * + * For libraries and scripts which have native functions, the embedder + * can provide a callback for mapping a native entry to a symbol. This callback + * maps a native function entry PC to the native function name. If no native + * entry symbol can be found, the callback should return NULL. + * + * The parameters to the native reverse resolver function are: + * \param nf A Dart_NativeFunction. + * + * \return A const UTF-8 string containing the symbol name or NULL. + * + * See Dart_SetNativeResolver. + */ +typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf); + +/** + * FFI Native C function pointer resolver callback. + * + * See Dart_SetFfiNativeResolver. + */ +typedef void* (*Dart_FfiNativeResolver)(const char* name, uintptr_t args_n); + +/* + * =========== + * Environment + * =========== + */ + +/** + * An environment lookup callback function. + * + * \param name The name of the value to lookup in the environment. + * + * \return A valid handle to a string if the name exists in the + * current environment or Dart_Null() if not. + */ +typedef Dart_Handle (*Dart_EnvironmentCallback)(Dart_Handle name); + +/** + * Sets the environment callback for the current isolate. This + * callback is used to lookup environment values by name in the + * current environment. This enables the embedder to supply values for + * the const constructors bool.fromEnvironment, int.fromEnvironment + * and String.fromEnvironment. + */ +DART_EXPORT Dart_Handle +Dart_SetEnvironmentCallback(Dart_EnvironmentCallback callback); + +/** + * Sets the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver A native entry resolver. + * + * \return A valid handle if the native resolver was set successfully. + */ +DART_EXPORT Dart_Handle +Dart_SetNativeResolver(Dart_Handle library, + Dart_NativeEntryResolver resolver, + Dart_NativeEntrySymbol symbol); +/* TODO(turnidge): Rename to Dart_LibrarySetNativeResolver? */ + +/** + * Returns the callback used to resolve native functions for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntryResolver + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle +Dart_GetNativeResolver(Dart_Handle library, Dart_NativeEntryResolver* resolver); + +/** + * Returns the callback used to resolve native function symbols for a library. + * + * \param library A library. + * \param resolver a pointer to a Dart_NativeEntrySymbol. + * + * \return A valid handle if the library was found. + */ +DART_EXPORT Dart_Handle Dart_GetNativeSymbol(Dart_Handle library, + Dart_NativeEntrySymbol* resolver); + +/** + * Sets the callback used to resolve FFI native functions for a library. + * The resolved functions are expected to be a C function pointer of the + * correct signature (as specified in the `@FfiNative()` function + * annotation in Dart code). + * + * NOTE: This is an experimental feature and might change in the future. + * + * \param library A library. + * \param resolver A native function resolver. + * + * \return A valid handle if the native resolver was set successfully. + */ +DART_EXPORT Dart_Handle +Dart_SetFfiNativeResolver(Dart_Handle library, Dart_FfiNativeResolver resolver); + +/* + * ===================== + * Scripts and Libraries + * ===================== + */ + +typedef enum { + Dart_kCanonicalizeUrl = 0, + Dart_kImportTag, + Dart_kKernelTag, +} Dart_LibraryTag; + +/** + * The library tag handler is a multi-purpose callback provided by the + * embedder to the Dart VM. The embedder implements the tag handler to + * provide the ability to load Dart scripts and imports. + * + * -- TAGS -- + * + * Dart_kCanonicalizeUrl + * + * This tag indicates that the embedder should canonicalize 'url' with + * respect to 'library'. For most embedders, the + * Dart_DefaultCanonicalizeUrl function is a sufficient implementation + * of this tag. The return value should be a string holding the + * canonicalized url. + * + * Dart_kImportTag + * + * This tag is used to load a library from IsolateMirror.loadUri. The embedder + * should call Dart_LoadLibraryFromKernel to provide the library to the VM. The + * return value should be an error or library (the result from + * Dart_LoadLibraryFromKernel). + * + * Dart_kKernelTag + * + * This tag is used to load the intermediate file (kernel) generated by + * the Dart front end. This tag is typically used when a 'hot-reload' + * of an application is needed and the VM is 'use dart front end' mode. + * The dart front end typically compiles all the scripts, imports and part + * files into one intermediate file hence we don't use the source/import or + * script tags. The return value should be an error or a TypedData containing + * the kernel bytes. + * + */ +typedef Dart_Handle (*Dart_LibraryTagHandler)( + Dart_LibraryTag tag, + Dart_Handle library_or_package_map_url, + Dart_Handle url); + +/** + * Sets library tag handler for the current isolate. This handler is + * used to handle the various tags encountered while loading libraries + * or scripts in the isolate. + * + * \param handler Handler code to be used for handling the various tags + * encountered while loading libraries or scripts in the isolate. + * + * \return If no error occurs, the handler is set for the isolate. + * Otherwise an error handle is returned. + * + * TODO(turnidge): Document. + */ +DART_EXPORT Dart_Handle +Dart_SetLibraryTagHandler(Dart_LibraryTagHandler handler); + +/** + * Handles deferred loading requests. When this handler is invoked, it should + * eventually load the deferred loading unit with the given id and call + * Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError. It is + * recommended that the loading occur asynchronously, but it is permitted to + * call Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError before the + * handler returns. + * + * If an error is returned, it will be propagated through + * `prefix.loadLibrary()`. This is useful for synchronous + * implementations, which must propagate any unwind errors from + * Dart_DeferredLoadComplete or Dart_DeferredLoadComplete. Otherwise the handler + * should return a non-error such as `Dart_Null()`. + */ +typedef Dart_Handle (*Dart_DeferredLoadHandler)(intptr_t loading_unit_id); + +/** + * Sets the deferred load handler for the current isolate. This handler is + * used to handle loading deferred imports in an AppJIT or AppAOT program. + */ +DART_EXPORT Dart_Handle +Dart_SetDeferredLoadHandler(Dart_DeferredLoadHandler handler); + +/** + * Notifies the VM that a deferred load completed successfully. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadComplete(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); + +/** + * Notifies the VM that a deferred load failed. This function + * will eventually cause the corresponding `prefix.loadLibrary()` futures to + * complete with an error. + * + * If `transient` is true, future invocations of `prefix.loadLibrary()` will + * trigger new load requests. If false, futures invocation will complete with + * the same error. + * + * Requires the current isolate to be the same current isolate during the + * invocation of the Dart_DeferredLoadHandler. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_DeferredLoadCompleteError(intptr_t loading_unit_id, + const char* error_message, + bool transient); + +/** + * Canonicalizes a url with respect to some library. + * + * The url is resolved with respect to the library's url and some url + * normalizations are performed. + * + * This canonicalization function should be sufficient for most + * embedders to implement the Dart_kCanonicalizeUrl tag. + * + * \param base_url The base url relative to which the url is + * being resolved. + * \param url The url being resolved and canonicalized. This + * parameter is a string handle. + * + * \return If no error occurs, a String object is returned. Otherwise + * an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_DefaultCanonicalizeUrl(Dart_Handle base_url, + Dart_Handle url); + +/** + * Loads the root library for the current isolate. + * + * Requires there to be no current root library. + * + * \param kernel_buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate group shutdown. + * \param kernel_size Length of the passed in buffer. + * + * \return A handle to the root library, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadScriptFromKernel(const uint8_t* kernel_buffer, intptr_t kernel_size); + +/** + * Gets the library for the root script for the current isolate. + * + * If the root script has not yet been set for the current isolate, + * this function returns Dart_Null(). This function never returns an + * error handle. + * + * \return Returns the root Library for the current isolate or Dart_Null(). + */ +DART_EXPORT Dart_Handle Dart_RootLibrary(void); + +/** + * Sets the root library for the current isolate. + * + * \return Returns an error handle if `library` is not a library handle. + */ +DART_EXPORT Dart_Handle Dart_SetRootLibrary(Dart_Handle library); + +/** + * Lookup or instantiate a legacy type by name and type arguments from a + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Lookup or instantiate a non-nullable type by name and type arguments from + * Library. + * + * \param library The library containing the class or interface. + * \param class_name The class name for the type. + * \param number_of_type_arguments Number of type arguments. + * For non parametric types the number of type arguments would be 0. + * \param type_arguments Pointer to an array of type arguments. + * For non parametric types a NULL would be passed in for this argument. + * + * \return If no error occurs, the type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle +Dart_GetNonNullableType(Dart_Handle library, + Dart_Handle class_name, + intptr_t number_of_type_arguments, + Dart_Handle* type_arguments); + +/** + * Creates a nullable version of the provided type. + * + * \param type The type to be converted to a nullable type. + * + * \return If no error occurs, a nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNullableType(Dart_Handle type); + +/** + * Creates a non-nullable version of the provided type. + * + * \param type The type to be converted to a non-nullable type. + * + * \return If no error occurs, a non-nullable type is returned. + * Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_TypeToNonNullableType(Dart_Handle type); + +/** + * A type's nullability. + * + * \param type A Dart type. + * \param result An out parameter containing the result of the check. True if + * the type is of the specified nullability, false otherwise. + * + * \return Returns an error handle if type is not of type Type. + */ +DART_EXPORT Dart_Handle Dart_IsNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsNonNullableType(Dart_Handle type, bool* result); +DART_EXPORT Dart_Handle Dart_IsLegacyType(Dart_Handle type, bool* result); + +/** + * Lookup a class or interface by name from a Library. + * + * \param library The library containing the class or interface. + * \param class_name The name of the class or interface. + * + * \return If no error occurs, the class or interface is + * returned. Otherwise an error handle is returned. + */ +DART_EXPORT Dart_Handle Dart_GetClass(Dart_Handle library, + Dart_Handle class_name); +/* TODO(asiva): The above method needs to be removed once all uses + * of it are removed from the embedder code. */ + +/** + * Returns an import path to a Library, such as "file:///test.dart" or + * "dart:core". + */ +DART_EXPORT Dart_Handle Dart_LibraryUrl(Dart_Handle library); + +/** + * Returns a URL from which a Library was loaded. + */ +DART_EXPORT Dart_Handle Dart_LibraryResolvedUrl(Dart_Handle library); + +/** + * \return An array of libraries. + */ +DART_EXPORT Dart_Handle Dart_GetLoadedLibraries(void); + +DART_EXPORT Dart_Handle Dart_LookupLibrary(Dart_Handle url); +/* TODO(turnidge): Consider returning Dart_Null() when the library is + * not found to distinguish that from a true error case. */ + +/** + * Report an loading error for the library. + * + * \param library The library that failed to load. + * \param error The Dart error instance containing the load error. + * + * \return If the VM handles the error, the return value is + * a null handle. If it doesn't handle the error, the error + * object is returned. + */ +DART_EXPORT Dart_Handle Dart_LibraryHandleError(Dart_Handle library, + Dart_Handle error); + +/** + * Called by the embedder to load a partial program. Does not set the root + * library. + * + * \param kernel_buffer A buffer which contains a kernel binary (see + * pkg/kernel/binary.md). Must remain valid until isolate shutdown. + * \param kernel_buffer_size Length of the passed in buffer. + * + * \return A handle to the main library of the compilation unit, or an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadLibraryFromKernel(const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_LoadLibrary(Dart_Handle kernel_buffer); + +/** + * Indicates that all outstanding load requests have been satisfied. + * This finalizes all the new classes loaded and optionally completes + * deferred library futures. + * + * Requires there to be a current isolate. + * + * \param complete_futures Specify true if all deferred library + * futures should be completed, false otherwise. + * + * \return Success if all classes have been finalized and deferred library + * futures are completed. Otherwise, returns an error. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_FinalizeLoading(bool complete_futures); + +/* + * ===== + * Peers + * ===== + */ + +/** + * The peer field is a lazily allocated field intended for storage of + * an uncommonly used values. Most instances types can have a peer + * field allocated. The exceptions are subtypes of Null, num, and + * bool. + */ + +/** + * Returns the value of peer field of 'object' in 'peer'. + * + * \param object An object. + * \param peer An out parameter that returns the value of the peer + * field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_GetPeer(Dart_Handle object, void** peer); + +/** + * Sets the value of the peer field of 'object' to the value of + * 'peer'. + * + * \param object An object. + * \param peer A value to store in the peer field. + * + * \return Returns an error if 'object' is a subtype of Null, num, or + * bool. + */ +DART_EXPORT Dart_Handle Dart_SetPeer(Dart_Handle object, void* peer); + +/* + * ====== + * Kernel + * ====== + */ + +/** + * Experimental support for Dart to Kernel parser isolate. + * + * TODO(hausner): Document finalized interface. + * + */ + +// TODO(33433): Remove kernel service from the embedding API. + +typedef enum { + Dart_KernelCompilationStatus_Unknown = -1, + Dart_KernelCompilationStatus_Ok = 0, + Dart_KernelCompilationStatus_Error = 1, + Dart_KernelCompilationStatus_Crash = 2, + Dart_KernelCompilationStatus_MsgFailed = 3, +} Dart_KernelCompilationStatus; + +typedef struct { + Dart_KernelCompilationStatus status; + bool null_safety; + char* error; + uint8_t* kernel; + intptr_t kernel_size; +} Dart_KernelCompilationResult; + +typedef enum { + Dart_KernelCompilationVerbosityLevel_Error = 0, + Dart_KernelCompilationVerbosityLevel_Warning, + Dart_KernelCompilationVerbosityLevel_Info, + Dart_KernelCompilationVerbosityLevel_All, +} Dart_KernelCompilationVerbosityLevel; + +DART_EXPORT bool Dart_IsKernelIsolate(Dart_Isolate isolate); +DART_EXPORT bool Dart_KernelIsolateIsRunning(void); +DART_EXPORT Dart_Port Dart_KernelPort(void); + +/** + * Compiles the given `script_uri` to a kernel file. + * + * \param platform_kernel A buffer containing the kernel of the platform (e.g. + * `vm_platform_strong.dill`). The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + * + * \param snapshot_compile Set to `true` when the compilation is for a snapshot. + * This is used by the frontend to determine if compilation related information + * should be printed to console (e.g., null safety mode). + * + * \param verbosity Specifies the logging behavior of the kernel compilation + * service. + * + * \return Returns the result of the compilation. + * + * On a successful compilation the returned [Dart_KernelCompilationResult] has + * a status of [Dart_KernelCompilationStatus_Ok] and the `kernel`/`kernel_size` + * fields are set. The caller takes ownership of the malloc()ed buffer. + * + * On a failed compilation the `error` might be set describing the reason for + * the failed compilation. The caller takes ownership of the malloc()ed + * error. + * + * Requires there to be a current isolate. + */ +DART_EXPORT Dart_KernelCompilationResult +Dart_CompileToKernel(const char* script_uri, + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size, + bool incremental_compile, + bool snapshot_compile, + const char* package_config, + Dart_KernelCompilationVerbosityLevel verbosity); + +typedef struct { + const char* uri; + const char* source; +} Dart_SourceFile; + +DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies(void); + +/** + * Sets the kernel buffer which will be used to load Dart SDK sources + * dynamically at runtime. + * + * \param platform_kernel A buffer containing kernel which has sources for the + * Dart SDK populated. Note: The VM does not take ownership of this memory. + * + * \param platform_kernel_size The length of the platform_kernel buffer. + */ +DART_EXPORT void Dart_SetDartLibrarySourcesKernel( + const uint8_t* platform_kernel, + const intptr_t platform_kernel_size); + +/** + * Detect the null safety opt-in status. + * + * When running from source, it is based on the opt-in status of `script_uri`. + * When running from a kernel buffer, it is based on the mode used when + * generating `kernel_buffer`. + * When running from an appJIT or AOT snapshot, it is based on the mode used + * when generating `snapshot_data`. + * + * \param script_uri Uri of the script that contains the source code + * + * \param package_config Uri of the package configuration file (either in format + * of .packages or .dart_tool/package_config.json) for the null safety + * detection to resolve package imports against. If this parameter is not + * passed the package resolution of the parent isolate should be used. + * + * \param original_working_directory current working directory when the VM + * process was launched, this is used to correctly resolve the path specified + * for package_config. + * + * \param snapshot_data Buffer containing the snapshot data of the + * isolate or NULL if no snapshot is provided. If provided, the buffers must + * remain valid until the isolate shuts down. + * + * \param snapshot_instructions Buffer containing the snapshot instructions of + * the isolate or NULL if no snapshot is provided. If provided, the buffers + * must remain valid until the isolate shuts down. + * + * \param kernel_buffer A buffer which contains a kernel/DIL program. Must + * remain valid until isolate shutdown. + * + * \param kernel_buffer_size The size of `kernel_buffer`. + * + * \return Returns true if the null safety is opted in by the input being + * run `script_uri`, `snapshot_data` or `kernel_buffer`. + * + */ +DART_EXPORT bool Dart_DetectNullSafety(const char* script_uri, + const char* package_config, + const char* original_working_directory, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size); + +#define DART_KERNEL_ISOLATE_NAME "kernel-service" + +/* + * ======= + * Service + * ======= + */ + +#define DART_VM_SERVICE_ISOLATE_NAME "vm-service" + +/** + * Returns true if isolate is the service isolate. + * + * \param isolate An isolate + * + * \return Returns true if 'isolate' is the service isolate. + */ +DART_EXPORT bool Dart_IsServiceIsolate(Dart_Isolate isolate); + +/** + * Writes the CPU profile to the timeline as a series of 'instant' events. + * + * Note that this is an expensive operation. + * + * \param main_port The main port of the Isolate whose profile samples to write. + * \param error An optional error, must be free()ed by caller. + * + * \return Returns true if the profile is successfully written and false + * otherwise. + */ +DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port, char** error); + +/* + * ============== + * Precompilation + * ============== + */ + +/** + * Compiles all functions reachable from entry points and marks + * the isolate to disallow future compilation. + * + * Entry points should be specified using `@pragma("vm:entry-point")` + * annotation. + * + * \return An error handle if a compilation error or runtime error running const + * constructors was encountered. + */ +DART_EXPORT Dart_Handle Dart_Precompile(void); + +typedef void (*Dart_CreateLoadingUnitCallback)( + void* callback_data, + intptr_t loading_unit_id, + void** write_callback_data, + void** write_debug_callback_data); +typedef void (*Dart_StreamingWriteCallback)(void* callback_data, + const uint8_t* buffer, + intptr_t size); +typedef void (*Dart_StreamingCloseCallback)(void* callback_data); + +DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id); + +// On Darwin systems, 'dlsym' adds an '_' to the beginning of the symbol name. +// Use the '...CSymbol' definitions for resolving through 'dlsym'. The actual +// symbol names in the objects are given by the '...AsmSymbol' definitions. +#if defined(__APPLE__) +#define kSnapshotBuildIdCSymbol "kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "kDartIsolateSnapshotBss" +#else +#define kSnapshotBuildIdCSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataCSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsCSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssCSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataCSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsCSymbol "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssCSymbol "_kDartIsolateSnapshotBss" +#endif + +#define kSnapshotBuildIdAsmSymbol "_kDartSnapshotBuildId" +#define kVmSnapshotDataAsmSymbol "_kDartVmSnapshotData" +#define kVmSnapshotInstructionsAsmSymbol "_kDartVmSnapshotInstructions" +#define kVmSnapshotBssAsmSymbol "_kDartVmSnapshotBss" +#define kIsolateSnapshotDataAsmSymbol "_kDartIsolateSnapshotData" +#define kIsolateSnapshotInstructionsAsmSymbol \ + "_kDartIsolateSnapshotInstructions" +#define kIsolateSnapshotBssAsmSymbol "_kDartIsolateSnapshotBss" + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an assembly file defining the symbols listed in the definitions + * above. + * + * The assembly should be compiled as a static or shared library and linked or + * loaded by the embedder. Running this snapshot requires a VM compiled with + * DART_PRECOMPILED_SNAPSHOT. The kDartVmSnapshotData and + * kDartVmSnapshotInstructions should be passed to Dart_Initialize. The + * kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions should be + * passed to Dart_CreateIsolateGroup. + * + * The callback will be invoked one or more times to provide the assembly code. + * + * If stripped is true, then the assembly code will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsAssemblies( + Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Creates a precompiled snapshot. + * - A root library must have been loaded. + * - Dart_Precompile must have been called. + * + * Outputs an ELF shared library defining the symbols + * - _kDartVmSnapshotData + * - _kDartVmSnapshotInstructions + * - _kDartIsolateSnapshotData + * - _kDartIsolateSnapshotInstructions + * + * The shared library should be dynamically loaded by the embedder. + * Running this snapshot requires a VM compiled with DART_PRECOMPILED_SNAPSHOT. + * The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to + * Dart_Initialize. The kDartIsolateSnapshotData and + * kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate. + * + * The callback will be invoked one or more times to provide the binary output. + * + * If stripped is true, then the binary output will not include DWARF + * debugging sections. + * + * If debug_callback_data is provided, debug_callback_data will be used with + * the callback to provide separate debugging information. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback, + void* callback_data, + bool stripped, + void* debug_callback_data); +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback, + void* next_callback_data, + bool stripped, + Dart_StreamingWriteCallback write_callback, + Dart_StreamingCloseCallback close_callback); + +/** + * Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes + * kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does + * not strip DWARF information from the generated assembly or allow for + * separate debug information. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateVMAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback, + void* callback_data); + +/** + * Sorts the class-ids in depth first traversal order of the inheritance + * tree. This is a costly operation, but it can make method dispatch + * more efficient and is done before writing snapshots. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_SortClasses(void); + +/** + * Creates a snapshot that caches compiled code and type feedback for faster + * startup and quicker warmup in a subsequent process. + * + * Outputs a snapshot in two pieces. The pieces should be passed to + * Dart_CreateIsolateGroup in a VM using the same VM snapshot pieces used in the + * current VM. The instructions piece must be loaded with read and execute + * permissions; the data piece may be loaded as read-only. + * + * - Requires the VM to have not been started with --precompilation. + * - Not supported when targeting IA32. + * - The VM writing the snapshot and the VM reading the snapshot must be the + * same version, must be built in the same DEBUG/RELEASE/PRODUCT mode, must + * be targeting the same architecture, and must both be in checked mode or + * both in unchecked mode. + * + * The buffers are scope allocated and are only valid until the next call to + * Dart_ExitScope. + * + * \return A valid handle if no error occurs during the operation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Like Dart_CreateAppJITSnapshotAsBlobs, but also creates a new VM snapshot. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_CreateCoreJITSnapshotAsBlobs( + uint8_t** vm_snapshot_data_buffer, + intptr_t* vm_snapshot_data_size, + uint8_t** vm_snapshot_instructions_buffer, + intptr_t* vm_snapshot_instructions_size, + uint8_t** isolate_snapshot_data_buffer, + intptr_t* isolate_snapshot_data_size, + uint8_t** isolate_snapshot_instructions_buffer, + intptr_t* isolate_snapshot_instructions_size); + +/** + * Get obfuscation map for precompiled code. + * + * Obfuscation map is encoded as a JSON array of pairs (original name, + * obfuscated name). + * + * \return Returns an error handler if the VM was built in a mode that does not + * support obfuscation. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle +Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length); + +/** + * Returns whether the VM only supports running from precompiled snapshots and + * not from any other kind of snapshot or from source (that is, the VM was + * compiled with DART_PRECOMPILED_RUNTIME). + */ +DART_EXPORT bool Dart_IsPrecompiledRuntime(void); + +/** + * Print a native stack trace. Used for crash handling. + * + * If context is NULL, prints the current stack trace. Otherwise, context + * should be a CONTEXT* (Windows) or ucontext_t* (POSIX) from a signal handler + * running on the current thread. + */ +DART_EXPORT void Dart_DumpNativeStackTrace(void* context); + +/** + * Indicate that the process is about to abort, and the Dart VM should not + * attempt to cleanup resources. + */ +DART_EXPORT void Dart_PrepareToAbort(void); + +/** + * Callback provided by the embedder that is used by the VM to + * produce footnotes appended to DWARF stack traces. + * + * Whenever VM formats a stack trace as a string it would call this callback + * passing raw program counters for each frame in the stack trace. + * + * Embedder can then return a string which if not-null will be appended to the + * formatted stack trace. + * + * Returned string is expected to be `malloc()` allocated. VM takes ownership + * of the returned string and will `free()` it. + * + * \param addresses raw program counter addresses for each frame + * \param count number of elements in the addresses array + */ +typedef char* (*Dart_DwarfStackTraceFootnoteCallback)(void* addresses[], + intptr_t count); + +/** + * Configure DWARF stack trace footnote callback. + */ +DART_EXPORT void Dart_SetDwarfStackTraceFootnoteCallback( + Dart_DwarfStackTraceFootnoteCallback callback); + +#endif /* INCLUDE_DART_API_H_ */ /* NOLINT */ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c new file mode 100644 index 00000000..c4a68f44 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#include "dart_api_dl.h" /* NOLINT */ +#include "dart_version.h" /* NOLINT */ +#include "internal/dart_api_dl_impl.h" /* NOLINT */ + +#include + +#define DART_API_DL_DEFINITIONS(name, R, A) name##_Type name##_DL = NULL; + +DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS) + +#undef DART_API_DL_DEFINITIONS + +typedef void* DartApiEntry_function; + +DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries, + const char* name) { + while (entries->name != NULL) { + if (strcmp(entries->name, name) == 0) return entries->function; + entries++; + } + return NULL; +} + +intptr_t Dart_InitializeApiDL(void* data) { + DartApi* dart_api_data = (DartApi*)data; + + if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) { + // If the DartVM we're running on does not have the same version as this + // file was compiled against, refuse to initialize. The symbols are not + // compatible. + return -1; + } + // Minor versions are allowed to be different. + // If the DartVM has a higher minor version, it will provide more symbols + // than we initialize here. + // If the DartVM has a lower minor version, it will not provide all symbols. + // In that case, we leave the missing symbols un-initialized. Those symbols + // should not be used by the Dart and native code. The client is responsible + // for checking the minor version number himself based on which symbols it + // is using. + // (If we would error out on this case, recompiling native code against a + // newer SDK would break all uses on older SDKs, which is too strict.) + + const DartApiEntry* dart_api_function_pointers = dart_api_data->functions; + +#define DART_API_DL_INIT(name, R, A) \ + name##_DL = \ + (name##_Type)(FindFunctionPointer(dart_api_function_pointers, #name)); + DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT) +#undef DART_API_DL_INIT + + return 0; +} diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h new file mode 100644 index 00000000..ba50d992 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_api_dl.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_API_DL_H_ +#define RUNTIME_INCLUDE_DART_API_DL_H_ + +#include "dart_api.h" /* NOLINT */ +#include "dart_native_api.h" /* NOLINT */ + +/** \mainpage Dynamically Linked Dart API + * + * This exposes a subset of symbols from dart_api.h and dart_native_api.h + * available in every Dart embedder through dynamic linking. + * + * All symbols are postfixed with _DL to indicate that they are dynamically + * linked and to prevent conflicts with the original symbol. + * + * Link `dart_api_dl.c` file into your library and invoke + * `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`. + */ + +DART_EXPORT intptr_t Dart_InitializeApiDL(void* data); + +// ============================================================================ +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types +// to trigger compile-time errors if the symbols in those files are updated +// without updating these. +// +// Function return and argument types, and typedefs are carbon copied. Structs +// are typechecked nominally in C/C++, so they are not copied, instead a +// comment is added to their definition. +typedef int64_t Dart_Port_DL; + +typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id, + Dart_CObject* message); + +// dart_native_api.h symbols can be called on any thread. +#define DART_NATIVE_API_DL_SYMBOLS(F) \ + /***** dart_native_api.h *****/ \ + /* Dart_Port */ \ + F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \ + F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \ + F(Dart_NewNativePort, Dart_Port_DL, \ + (const char* name, Dart_NativeMessageHandler_DL handler, \ + bool handle_concurrently)) \ + F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id)) + +// dart_api.h symbols can only be called on Dart threads. +#define DART_API_DL_SYMBOLS(F) \ + /***** dart_api.h *****/ \ + /* Errors */ \ + F(Dart_IsError, bool, (Dart_Handle handle)) \ + F(Dart_IsApiError, bool, (Dart_Handle handle)) \ + F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle)) \ + F(Dart_IsCompilationError, bool, (Dart_Handle handle)) \ + F(Dart_IsFatalError, bool, (Dart_Handle handle)) \ + F(Dart_GetError, const char*, (Dart_Handle handle)) \ + F(Dart_ErrorHasException, bool, (Dart_Handle handle)) \ + F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle)) \ + F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle)) \ + F(Dart_NewApiError, Dart_Handle, (const char* error)) \ + F(Dart_NewCompilationError, Dart_Handle, (const char* error)) \ + F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception)) \ + F(Dart_PropagateError, void, (Dart_Handle handle)) \ + /* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \ + F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object)) \ + F(Dart_HandleFromWeakPersistent, Dart_Handle, \ + (Dart_WeakPersistentHandle object)) \ + F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object)) \ + F(Dart_SetPersistentHandle, void, \ + (Dart_PersistentHandle obj1, Dart_Handle obj2)) \ + F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object)) \ + F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle, \ + (Dart_Handle object, void* peer, intptr_t external_allocation_size, \ + Dart_HandleFinalizer callback)) \ + F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \ + F(Dart_UpdateExternalSize, void, \ + (Dart_WeakPersistentHandle object, intptr_t external_allocation_size)) \ + F(Dart_NewFinalizableHandle, Dart_FinalizableHandle, \ + (Dart_Handle object, void* peer, intptr_t external_allocation_size, \ + Dart_HandleFinalizer callback)) \ + F(Dart_DeleteFinalizableHandle, void, \ + (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object)) \ + F(Dart_UpdateFinalizableExternalSize, void, \ + (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object, \ + intptr_t external_allocation_size)) \ + /* Dart_Port */ \ + F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \ + F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \ + F(Dart_SendPortGetId, Dart_Handle, \ + (Dart_Handle port, Dart_Port_DL * port_id)) \ + /* Scopes */ \ + F(Dart_EnterScope, void, (void)) \ + F(Dart_ExitScope, void, (void)) \ + /* Objects */ \ + F(Dart_IsNull, bool, (Dart_Handle)) + +#define DART_API_ALL_DL_SYMBOLS(F) \ + DART_NATIVE_API_DL_SYMBOLS(F) \ + DART_API_DL_SYMBOLS(F) +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// End of verbatim copy. +// ============================================================================ + +// Copy of definition of DART_EXPORT without 'used' attribute. +// +// The 'used' attribute cannot be used with DART_API_ALL_DL_SYMBOLS because +// they are not function declarations, but variable declarations with a +// function pointer type. +// +// The function pointer variables are initialized with the addresses of the +// functions in the VM. If we were to use function declarations instead, we +// would need to forward the call to the VM adding indirection. +#if defined(__CYGWIN__) +#error Tool chain and platform not supported. +#elif defined(_WIN32) +#if defined(DART_SHARED_LIB) +#define DART_EXPORT_DL DART_EXTERN_C __declspec(dllexport) +#else +#define DART_EXPORT_DL DART_EXTERN_C +#endif +#else +#if __GNUC__ >= 4 +#if defined(DART_SHARED_LIB) +#define DART_EXPORT_DL DART_EXTERN_C __attribute__((visibility("default"))) +#else +#define DART_EXPORT_DL DART_EXTERN_C +#endif +#else +#error Tool chain not supported. +#endif +#endif + +#define DART_API_DL_DECLARATIONS(name, R, A) \ + typedef R(*name##_Type) A; \ + DART_EXPORT_DL name##_Type name##_DL; + +DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS) + +#undef DART_API_DL_DECLARATIONS + +#undef DART_EXPORT_DL + +#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h new file mode 100644 index 00000000..e565ebf6 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_embedder_api.h @@ -0,0 +1,108 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_INCLUDE_DART_EMBEDDER_API_H_ +#define RUNTIME_INCLUDE_DART_EMBEDDER_API_H_ + +#include "include/dart_api.h" +#include "include/dart_tools_api.h" + +namespace dart { +namespace embedder { + +// Initialize all subsystems of the embedder. +// +// Must be called before the `Dart_Initialize()` call to initialize the +// Dart VM. +// +// Returns true on success and false otherwise, in which case error would +// contain error message. +DART_WARN_UNUSED_RESULT bool InitOnce(char** error); + +// Cleans up all subsystems of the embedder. +// +// Must be called after the `Dart_Cleanup()` call to initialize the +// Dart VM. +void Cleanup(); + +// Common arguments that are passed to isolate creation callback and to +// API methods that create isolates. +struct IsolateCreationData { + // URI for the main script that will be running in the isolate. + const char* script_uri; + + // Advisory name of the main method that will be run by isolate. + // Only used for error messages. + const char* main; + + // Isolate creation flags. Might be absent. + Dart_IsolateFlags* flags; + + // Isolate group callback data. + void* isolate_group_data; + + // Isolate callback data. + void* isolate_data; +}; + +// Create and initialize kernel-service isolate. This method should be used +// when VM invokes isolate creation callback with DART_KERNEL_ISOLATE_NAME as +// script_uri. +// The isolate is created from the given snapshot (might be kernel data or +// app-jit snapshot). +DART_WARN_UNUSED_RESULT Dart_Isolate +CreateKernelServiceIsolate(const IsolateCreationData& data, + const uint8_t* buffer, + intptr_t buffer_size, + char** error); + +// Service isolate configuration. +struct VmServiceConfiguration { + enum { + kBindHttpServerToAFreePort = 0, + kDoNotAutoStartHttpServer = -1 + }; + + // Address to which HTTP server will be bound. + const char* ip; + + // Default port. See enum above for special values. + int port; + + // If non-null, connection information for the VM service will be output to a + // file in JSON format at the location specified. + const char* write_service_info_filename; + + // TODO(vegorov) document these ones. + bool dev_mode; + bool deterministic; + bool disable_auth_codes; +}; + +// Create and initialize vm-service isolate from the given AOT snapshot, which +// is expected to contain all necessary 'vm-service' libraries. +// This method should be used when VM invokes isolate creation callback with +// DART_VM_SERVICE_ISOLATE_NAME as script_uri. +DART_WARN_UNUSED_RESULT Dart_Isolate +CreateVmServiceIsolate(const IsolateCreationData& data, + const VmServiceConfiguration& config, + const uint8_t* isolate_data, + const uint8_t* isolate_instr, + char** error); + +// Create and initialize vm-service isolate from the given kernel binary, which +// is expected to contain all necessary 'vm-service' libraries. +// This method should be used when VM invokes isolate creation callback with +// DART_VM_SERVICE_ISOLATE_NAME as script_uri. +DART_WARN_UNUSED_RESULT Dart_Isolate +CreateVmServiceIsolateFromKernel(const IsolateCreationData& data, + const VmServiceConfiguration& config, + const uint8_t* kernel_buffer, + intptr_t kernel_buffer_size, + char** error); + +} // namespace embedder +} // namespace dart + +#endif // RUNTIME_INCLUDE_DART_EMBEDDER_API_H_ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h new file mode 100644 index 00000000..79194e03 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_native_api.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_ +#define RUNTIME_INCLUDE_DART_NATIVE_API_H_ + +#include "dart_api.h" /* NOLINT */ + +/* + * ========================================== + * Message sending/receiving from native code + * ========================================== + */ + +/** + * A Dart_CObject is used for representing Dart objects as native C + * data outside the Dart heap. These objects are totally detached from + * the Dart heap. Only a subset of the Dart objects have a + * representation as a Dart_CObject. + * + * The string encoding in the 'value.as_string' is UTF-8. + * + * All the different types from dart:typed_data are exposed as type + * kTypedData. The specific type from dart:typed_data is in the type + * field of the as_typed_data structure. The length in the + * as_typed_data structure is always in bytes. + * + * The data for kTypedData is copied on message send and ownership remains with + * the caller. The ownership of data for kExternalTyped is passed to the VM on + * message send and returned when the VM invokes the + * Dart_HandleFinalizer callback; a non-NULL callback must be provided. + * + * Note that Dart_CObject_kNativePointer is intended for internal use by + * dart:io implementation and has no connection to dart:ffi Pointer class. + * It represents a pointer to a native resource of a known type. + * The receiving side will only see this pointer as an integer and will not + * see the specified finalizer. + * The specified finalizer will only be invoked if the message is not delivered. + */ +typedef enum { + Dart_CObject_kNull = 0, + Dart_CObject_kBool, + Dart_CObject_kInt32, + Dart_CObject_kInt64, + Dart_CObject_kDouble, + Dart_CObject_kString, + Dart_CObject_kArray, + Dart_CObject_kTypedData, + Dart_CObject_kExternalTypedData, + Dart_CObject_kSendPort, + Dart_CObject_kCapability, + Dart_CObject_kNativePointer, + Dart_CObject_kUnsupported, + Dart_CObject_kUnmodifiableExternalTypedData, + Dart_CObject_kNumberOfTypes +} Dart_CObject_Type; +// This enum is versioned by DART_API_DL_MAJOR_VERSION, only add at the end +// and bump the DART_API_DL_MINOR_VERSION. + +typedef struct _Dart_CObject { + Dart_CObject_Type type; + union { + bool as_bool; + int32_t as_int32; + int64_t as_int64; + double as_double; + const char* as_string; + struct { + Dart_Port id; + Dart_Port origin_id; + } as_send_port; + struct { + int64_t id; + } as_capability; + struct { + intptr_t length; + struct _Dart_CObject** values; + } as_array; + struct { + Dart_TypedData_Type type; + intptr_t length; /* in elements, not bytes */ + const uint8_t* values; + } as_typed_data; + struct { + Dart_TypedData_Type type; + intptr_t length; /* in elements, not bytes */ + uint8_t* data; + void* peer; + Dart_HandleFinalizer callback; + } as_external_typed_data; + struct { + intptr_t ptr; + intptr_t size; + Dart_HandleFinalizer callback; + } as_native_pointer; + } value; +} Dart_CObject; +// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when +// changing this struct. + +/** + * Posts a message on some port. The message will contain the Dart_CObject + * object graph rooted in 'message'. + * + * While the message is being sent the state of the graph of Dart_CObject + * structures rooted in 'message' should not be accessed, as the message + * generation will make temporary modifications to the data. When the message + * has been sent the graph will be fully restored. + * + * If true is returned, the message was enqueued, and finalizers for external + * typed data will eventually run, even if the receiving isolate shuts down + * before processing the message. If false is returned, the message was not + * enqueued and ownership of external typed data in the message remains with the + * caller. + * + * This function may be called on any thread when the VM is running (that is, + * after Dart_Initialize has returned and before Dart_Cleanup has been called). + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message); + +/** + * Posts a message on some port. The message will contain the integer 'message'. + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message); + +/** + * A native message handler. + * + * This handler is associated with a native port by calling + * Dart_NewNativePort. + * + * The message received is decoded into the message structure. The + * lifetime of the message data is controlled by the caller. All the + * data references from the message are allocated by the caller and + * will be reclaimed when returning to it. + */ +typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id, + Dart_CObject* message); + +/** + * Creates a new native port. When messages are received on this + * native port, then they will be dispatched to the provided native + * message handler. + * + * \param name The name of this port in debugging messages. + * \param handler The C handler to run when messages arrive on the port. + * \param handle_concurrently Is it okay to process requests on this + * native port concurrently? + * + * \return If successful, returns the port id for the native port. In + * case of error, returns ILLEGAL_PORT. + */ +DART_EXPORT Dart_Port Dart_NewNativePort(const char* name, + Dart_NativeMessageHandler handler, + bool handle_concurrently); +/* TODO(turnidge): Currently handle_concurrently is ignored. */ + +/** + * Closes the native port with the given id. + * + * The port must have been allocated by a call to Dart_NewNativePort. + * + * \param native_port_id The id of the native port to close. + * + * \return Returns true if the port was closed successfully. + */ +DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id); + +/* + * ================== + * Verification Tools + * ================== + */ + +/** + * Forces all loaded classes and functions to be compiled eagerly in + * the current isolate.. + * + * TODO(turnidge): Document. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(void); + +/** + * Finalizes all classes. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(void); + +/* This function is intentionally undocumented. + * + * It should not be used outside internal tests. + */ +DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg); + +#endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h new file mode 100644 index 00000000..6136a416 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_tools_api.h @@ -0,0 +1,582 @@ +// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_INCLUDE_DART_TOOLS_API_H_ +#define RUNTIME_INCLUDE_DART_TOOLS_API_H_ + +#include "dart_api.h" /* NOLINT */ + +/** \mainpage Dart Tools Embedding API Reference + * + * This reference describes the Dart embedding API for tools. Tools include + * a debugger, service protocol, and timeline. + * + * NOTE: The APIs described in this file are unstable and subject to change. + * + * This reference is generated from the header include/dart_tools_api.h. + */ + +/* + * ======== + * Debugger + * ======== + */ + +/** + * ILLEGAL_ISOLATE_ID is a number guaranteed never to be associated with a + * valid isolate. + */ +#define ILLEGAL_ISOLATE_ID ILLEGAL_PORT + +/** + * ILLEGAL_ISOLATE_GROUP_ID is a number guaranteed never to be associated with a + * valid isolate group. + */ +#define ILLEGAL_ISOLATE_GROUP_ID 0 + +/* + * ======= + * Service + * ======= + */ + +/** + * A service request callback function. + * + * These callbacks, registered by the embedder, are called when the VM receives + * a service request it can't handle and the service request command name + * matches one of the embedder registered handlers. + * + * The return value of the callback indicates whether the response + * should be used as a regular result or an error result. + * Specifically, if the callback returns true, a regular JSON-RPC + * response is built in the following way: + * + * { + * "jsonrpc": "2.0", + * "result": , + * "id": , + * } + * + * If the callback returns false, a JSON-RPC error is built like this: + * + * { + * "jsonrpc": "2.0", + * "error": , + * "id": , + * } + * + * \param method The rpc method name. + * \param param_keys Service requests can have key-value pair parameters. The + * keys and values are flattened and stored in arrays. + * \param param_values The values associated with the keys. + * \param num_params The length of the param_keys and param_values arrays. + * \param user_data The user_data pointer registered with this handler. + * \param result A C string containing a valid JSON object. The returned + * pointer will be freed by the VM by calling free. + * + * \return True if the result is a regular JSON-RPC response, false if the + * result is a JSON-RPC error. + */ +typedef bool (*Dart_ServiceRequestCallback)(const char* method, + const char** param_keys, + const char** param_values, + intptr_t num_params, + void* user_data, + const char** json_object); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc on a specific isolate. The callback will + * be invoked with the current isolate set to the request target. + * + * \param method The name of the method that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks with the same name are registered, only + * the last callback registered will be remembered. + */ +DART_EXPORT void Dart_RegisterIsolateServiceRequestCallback( + const char* method, + Dart_ServiceRequestCallback callback, + void* user_data); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc. The callback will be invoked without a + * current isolate. + * + * \param method The name of the command that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks with the same name are registered, only + * the last callback registered will be remembered. + */ +DART_EXPORT void Dart_RegisterRootServiceRequestCallback( + const char* method, + Dart_ServiceRequestCallback callback, + void* user_data); + +/** + * Embedder information which can be requested by the VM for internal or + * reporting purposes. + * + * The pointers in this structure are not going to be cached or freed by the VM. + */ + + #define DART_EMBEDDER_INFORMATION_CURRENT_VERSION (0x00000001) + +typedef struct { + int32_t version; + const char* name; // [optional] The name of the embedder + int64_t current_rss; // [optional] the current RSS of the embedder + int64_t max_rss; // [optional] the maximum RSS of the embedder +} Dart_EmbedderInformation; + +/** + * Callback provided by the embedder that is used by the VM to request + * information. + * + * \return Returns a pointer to a Dart_EmbedderInformation structure. + * The embedder keeps the ownership of the structure and any field in it. + * The embedder must ensure that the structure will remain valid until the + * next invocation of the callback. + */ +typedef void (*Dart_EmbedderInformationCallback)( + Dart_EmbedderInformation* info); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc. The callback will be invoked without a + * current isolate. + * + * \param method The name of the command that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks are registered, only the last callback registered + * will be remembered. + */ +DART_EXPORT void Dart_SetEmbedderInformationCallback( + Dart_EmbedderInformationCallback callback); + +/** + * Invoke a vm-service method and wait for its result. + * + * \param request_json The utf8-encoded json-rpc request. + * \param request_json_length The length of the json-rpc request. + * + * \param response_json The returned utf8-encoded json response, must be + * free()ed by caller. + * \param response_json_length The length of the returned json response. + * \param error An optional error, must be free()ed by caller. + * + * \return Whether the call was successfully performed. + * + * NOTE: This method does not need a current isolate and must not have the + * vm-isolate being the current isolate. It must be called after + * Dart_Initialize() and before Dart_Cleanup(). + */ +DART_EXPORT bool Dart_InvokeVMServiceMethod(uint8_t* request_json, + intptr_t request_json_length, + uint8_t** response_json, + intptr_t* response_json_length, + char** error); + +/* + * ======== + * Event Streams + * ======== + */ + +/** + * A callback invoked when the VM service gets a request to listen to + * some stream. + * + * \return Returns true iff the embedder supports the named stream id. + */ +typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id); + +/** + * A callback invoked when the VM service gets a request to cancel + * some stream. + */ +typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id); + +/** + * Adds VM service stream callbacks. + * + * \param listen_callback A function pointer to a listen callback function. + * A listen callback function should not be already set when this function + * is called. A NULL value removes the existing listen callback function + * if any. + * + * \param cancel_callback A function pointer to a cancel callback function. + * A cancel callback function should not be already set when this function + * is called. A NULL value removes the existing cancel callback function + * if any. + * + * \return Success if the callbacks were added. Otherwise, returns an + * error handle. + */ +DART_EXPORT char* Dart_SetServiceStreamCallbacks( + Dart_ServiceStreamListenCallback listen_callback, + Dart_ServiceStreamCancelCallback cancel_callback); + +/** + * Sends a data event to clients of the VM Service. + * + * A data event is used to pass an array of bytes to subscribed VM + * Service clients. For example, in the standalone embedder, this is + * function used to provide WriteEvents on the Stdout and Stderr + * streams. + * + * If the embedder passes in a stream id for which no client is + * subscribed, then the event is ignored. + * + * \param stream_id The id of the stream on which to post the event. + * + * \param event_kind A string identifying what kind of event this is. + * For example, 'WriteEvent'. + * + * \param bytes A pointer to an array of bytes. + * + * \param bytes_length The length of the byte array. + * + * \return NULL if the arguments are well formed. Otherwise, returns an + * error string. The caller is responsible for freeing the error message. + */ +DART_EXPORT char* Dart_ServiceSendDataEvent(const char* stream_id, + const char* event_kind, + const uint8_t* bytes, + intptr_t bytes_length); + +/* + * ======== + * Reload support + * ======== + * + * These functions are used to implement reloading in the Dart VM. + * This is an experimental feature, so embedders should be prepared + * for these functions to change. + */ + +/** + * A callback which determines whether the file at some url has been + * modified since some time. If the file cannot be found, true should + * be returned. + */ +typedef bool (*Dart_FileModifiedCallback)(const char* url, int64_t since); + +DART_EXPORT char* Dart_SetFileModifiedCallback( + Dart_FileModifiedCallback file_modified_callback); + +/** + * Returns true if isolate is currently reloading. + */ +DART_EXPORT bool Dart_IsReloading(); + +/* + * ======== + * Timeline + * ======== + */ + +/** + * Enable tracking of specified timeline category. This is operational + * only when systrace timeline functionality is turned on. + * + * \param categories A comma separated list of categories that need to + * be enabled, the categories are + * "all" : All categories + * "API" - Execution of Dart C API functions + * "Compiler" - Execution of Dart JIT compiler + * "CompilerVerbose" - More detailed Execution of Dart JIT compiler + * "Dart" - Execution of Dart code + * "Debugger" - Execution of Dart debugger + * "Embedder" - Execution of Dart embedder code + * "GC" - Execution of Dart Garbage Collector + * "Isolate" - Dart Isolate lifecycle execution + * "VM" - Execution in Dart VM runtime code + * "" - None + * + * When "all" is specified all the categories are enabled. + * When a comma separated list of categories is specified, the categories + * that are specified will be enabled and the rest will be disabled. + * When "" is specified all the categories are disabled. + * The category names are case sensitive. + * eg: Dart_EnableTimelineCategory("all"); + * Dart_EnableTimelineCategory("GC,API,Isolate"); + * Dart_EnableTimelineCategory("GC,Debugger,Dart"); + * + * \return True if the categories were successfully enabled, False otherwise. + */ +DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories); + +/** + * Returns a timestamp in microseconds. This timestamp is suitable for + * passing into the timeline system, and uses the same monotonic clock + * as dart:developer's Timeline.now. + * + * \return A timestamp that can be passed to the timeline system. + */ +DART_EXPORT int64_t Dart_TimelineGetMicros(); + +/** + * Returns a raw timestamp in from the monotonic clock. + * + * \return A raw timestamp from the monotonic clock. + */ +DART_EXPORT int64_t Dart_TimelineGetTicks(); + +/** + * Returns the frequency of the monotonic clock. + * + * \return The frequency of the monotonic clock. + */ +DART_EXPORT int64_t Dart_TimelineGetTicksFrequency(); + +typedef enum { + Dart_Timeline_Event_Begin, // Phase = 'B'. + Dart_Timeline_Event_End, // Phase = 'E'. + Dart_Timeline_Event_Instant, // Phase = 'i'. + Dart_Timeline_Event_Duration, // Phase = 'X'. + Dart_Timeline_Event_Async_Begin, // Phase = 'b'. + Dart_Timeline_Event_Async_End, // Phase = 'e'. + Dart_Timeline_Event_Async_Instant, // Phase = 'n'. + Dart_Timeline_Event_Counter, // Phase = 'C'. + Dart_Timeline_Event_Flow_Begin, // Phase = 's'. + Dart_Timeline_Event_Flow_Step, // Phase = 't'. + Dart_Timeline_Event_Flow_End, // Phase = 'f'. +} Dart_Timeline_Event_Type; + +/** + * Add a timeline event to the embedder stream. + * + * \param label The name of the event. Its lifetime must extend at least until + * Dart_Cleanup. + * \param timestamp0 The first timestamp of the event. + * \param timestamp1_or_async_id The second timestamp of the event or + * the async id. + * \param argument_count The number of argument names and values. + * \param argument_names An array of names of the arguments. The lifetime of the + * names must extend at least until Dart_Cleanup. The array may be reclaimed + * when this call returns. + * \param argument_values An array of values of the arguments. The values and + * the array may be reclaimed when this call returns. + */ +DART_EXPORT void Dart_TimelineEvent(const char* label, + int64_t timestamp0, + int64_t timestamp1_or_async_id, + Dart_Timeline_Event_Type type, + intptr_t argument_count, + const char** argument_names, + const char** argument_values); + +/** + * Associates a name with the current thread. This name will be used to name + * threads in the timeline. Can only be called after a call to Dart_Initialize. + * + * \param name The name of the thread. + */ +DART_EXPORT void Dart_SetThreadName(const char* name); + +typedef struct { + const char* name; + const char* value; +} Dart_TimelineRecorderEvent_Argument; + +#define DART_TIMELINE_RECORDER_CURRENT_VERSION (0x00000001) + +typedef struct { + /* Set to DART_TIMELINE_RECORDER_CURRENT_VERSION */ + int32_t version; + + /* The event's type / phase. */ + Dart_Timeline_Event_Type type; + + /* The event's timestamp according to the same clock as + * Dart_TimelineGetMicros. For a duration event, this is the beginning time. + */ + int64_t timestamp0; + + /* For a duration event, this is the end time. For an async event, this is the + * async id. */ + int64_t timestamp1_or_async_id; + + /* The current isolate of the event, as if by Dart_GetMainPortId, or + * ILLEGAL_PORT if the event had no current isolate. */ + Dart_Port isolate; + + /* The current isolate group of the event, as if by + * Dart_CurrentIsolateGroupId, or ILLEGAL_PORT if the event had no current + * isolate group. */ + Dart_IsolateGroupId isolate_group; + + /* The name / label of the event. */ + const char* label; + + /* The stream / category of the event. */ + const char* stream; + + intptr_t argument_count; + Dart_TimelineRecorderEvent_Argument* arguments; +} Dart_TimelineRecorderEvent; + +/** + * Callback provided by the embedder to handle the completion of timeline + * events. + * + * \param event A timeline event that has just been completed. The VM keeps + * ownership of the event and any field in it (i.e., the embedder should copy + * any values it needs after the callback returns). + */ +typedef void (*Dart_TimelineRecorderCallback)( + Dart_TimelineRecorderEvent* event); + +/** + * Register a `Dart_TimelineRecorderCallback` to be called as timeline events + * are completed. + * + * The callback will be invoked without a current isolate. + * + * The callback will be invoked on the thread completing the event. Because + * `Dart_TimelineEvent` may be called by any thread, the callback may be called + * on any thread. + * + * The callback may be invoked at any time after `Dart_Initialize` is called and + * before `Dart_Cleanup` returns. + * + * If multiple callbacks are registered, only the last callback registered + * will be remembered. Providing a NULL callback will clear the registration + * (i.e., a NULL callback produced a no-op instead of a crash). + * + * Setting a callback is insufficient to receive events through the callback. The + * VM flag `timeline_recorder` must also be set to `callback`. + */ +DART_EXPORT void Dart_SetTimelineRecorderCallback( + Dart_TimelineRecorderCallback callback); + +/* + * ======= + * Metrics + * ======= + */ + +/** + * Return metrics gathered for the VM and individual isolates. + */ +DART_EXPORT int64_t +Dart_IsolateGroupHeapOldUsedMetric(Dart_IsolateGroup group); // Byte +DART_EXPORT int64_t +Dart_IsolateGroupHeapOldCapacityMetric(Dart_IsolateGroup group); // Byte +DART_EXPORT int64_t +Dart_IsolateGroupHeapOldExternalMetric(Dart_IsolateGroup group); // Byte +DART_EXPORT int64_t +Dart_IsolateGroupHeapNewUsedMetric(Dart_IsolateGroup group); // Byte +DART_EXPORT int64_t +Dart_IsolateGroupHeapNewCapacityMetric(Dart_IsolateGroup group); // Byte +DART_EXPORT int64_t +Dart_IsolateGroupHeapNewExternalMetric(Dart_IsolateGroup group); // Byte + +/* + * ======== + * UserTags + * ======== + */ + +/* + * Gets the current isolate's currently set UserTag instance. + * + * \return The currently set UserTag instance. + */ +DART_EXPORT Dart_Handle Dart_GetCurrentUserTag(); + +/* + * Gets the current isolate's default UserTag instance. + * + * \return The default UserTag with label 'Default' + */ +DART_EXPORT Dart_Handle Dart_GetDefaultUserTag(); + +/* + * Creates a new UserTag instance. + * + * \param label The name of the new UserTag. + * + * \return The newly created UserTag instance or an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewUserTag(const char* label); + +/* + * Updates the current isolate's UserTag to a new value. + * + * \param user_tag The UserTag to be set as the current UserTag. + * + * \return The previously set UserTag instance or an error handle. + */ +DART_EXPORT Dart_Handle Dart_SetCurrentUserTag(Dart_Handle user_tag); + +/* + * Returns the label of a given UserTag instance. + * + * \param user_tag The UserTag from which the label will be retrieved. + * + * \return The UserTag's label. NULL if the user_tag is invalid. The caller is + * responsible for freeing the returned label. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_GetUserTagLabel( + Dart_Handle user_tag); + +/* + * ======= + * Heap Snapshot + * ======= + */ + +/** + * Callback provided by the caller of `Dart_WriteHeapSnapshot` which is + * used to write out chunks of the requested heap snapshot. + * + * \param context An opaque context which was passed to `Dart_WriteHeapSnapshot` + * together with this callback. + * + * \param buffer Pointer to the buffer containing a chunk of the snapshot. + * The callback owns the buffer and needs to `free` it. + * + * \param size Number of bytes in the `buffer` to be written. + * + * \param is_last Set to `true` for the last chunk. The callback will not + * be invoked again after it was invoked once with `is_last` set to `true`. + */ +typedef void (*Dart_HeapSnapshotWriteChunkCallback)(void* context, + uint8_t* buffer, + intptr_t size, + bool is_last); + +/** + * Generate heap snapshot of the current isolate group and stream it into the + * given `callback`. VM would produce snapshot in chunks and send these chunks + * one by one back to the embedder by invoking the provided `callback`. + * + * This API enables embedder to stream snapshot into a file or socket without + * allocating a buffer to hold the whole snapshot in memory. + * + * The isolate group will be paused for the duration of this operation. + * + * \param write Callback used to write chunks of the heap snapshot. + * + * \param context Opaque context which would be passed on each invocation of + * `write` callback. + * + * \returns `nullptr` if the operation is successful otherwise error message. + * Caller owns error message string and needs to `free` it. + */ +DART_EXPORT char* Dart_WriteHeapSnapshot( + Dart_HeapSnapshotWriteChunkCallback write, + void* context); + +#endif // RUNTIME_INCLUDE_DART_TOOLS_API_H_ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h new file mode 100644 index 00000000..680fb539 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/dart_version.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_VERSION_H_ +#define RUNTIME_INCLUDE_DART_VERSION_H_ + +// On breaking changes the major version is increased. +// On backwards compatible changes the minor version is increased. +// The versioning covers the symbols exposed in dart_api_dl.h +#define DART_API_DL_MAJOR_VERSION 2 +#define DART_API_DL_MINOR_VERSION 2 + +#endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */ diff --git a/example/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h b/example/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h new file mode 100644 index 00000000..e4a56893 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/dart_api/internal/dart_api_dl_impl.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ +#define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ + +typedef struct { + const char* name; + void (*function)(void); +} DartApiEntry; + +typedef struct { + const int major; + const int minor; + const DartApiEntry* const functions; +} DartApi; + +#endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */ diff --git a/example/native/hub/src/bridge/bridge_engine/ffi/io.rs b/example/native/hub/src/bridge/bridge_engine/ffi/io.rs new file mode 100644 index 00000000..3a966960 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/ffi/io.rs @@ -0,0 +1,104 @@ +use crate::bridge::bridge_engine::Channel; + +pub use super::DartAbi; +pub use super::MessagePort; +pub use allo_isolate::*; +use dart_sys::Dart_DeletePersistentHandle_DL; +use dart_sys::Dart_Handle; +use dart_sys::Dart_HandleFromPersistent_DL; +use dart_sys::Dart_InitializeApiDL; +use dart_sys::Dart_NewPersistentHandle_DL; +use dart_sys::Dart_PersistentHandle; +use libc::c_void; + +/// # Safety +/// +/// This function should never be called manually. +#[no_mangle] +pub unsafe extern "C" fn new_dart_opaque(handle: Dart_Handle) -> usize { + Dart_NewPersistentHandle_DL.expect("dart_api_dl has not been initialized")(handle) as _ +} + +/// # Safety +/// +/// This function should never be called manually. +#[no_mangle] +pub unsafe extern "C" fn get_dart_object(ptr: usize) -> Dart_Handle { + let handle = ptr as _; + let res = Dart_HandleFromPersistent_DL.expect("dart_api_dl has not been initialized")(handle); + Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(handle); + res +} + +/// # Safety +/// +/// This function should never be called manually. +#[no_mangle] +pub unsafe extern "C" fn drop_dart_object(ptr: usize) { + Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(ptr as _); +} + +/// # Safety +/// +/// This function should never be called manually. +#[no_mangle] +pub unsafe extern "C" fn init_frb_dart_api_dl(data: *mut c_void) -> isize { + Dart_InitializeApiDL(data) +} + +#[derive(Debug)] +/// Option for correct drop. +pub struct DartHandleWrap(Option); + +impl DartHandleWrap { + pub fn from_raw(ptr: Dart_PersistentHandle) -> Self { + Self(Some(ptr)) + } + + pub fn into_raw(mut self) -> Dart_PersistentHandle { + self.0.take().unwrap() + } +} + +impl From for Dart_PersistentHandle { + fn from(warp: DartHandleWrap) -> Self { + warp.into_raw() + } +} + +impl Drop for DartHandleWrap { + fn drop(&mut self) { + if let Some(inner) = self.0 { + unsafe { + Dart_DeletePersistentHandle_DL.expect("dart_api_dl has not been initialized")(inner) + } + } + } +} + +#[derive(Debug)] +pub struct DartOpaqueBase { + inner: DartHandleWrap, + drop_port: Option, +} + +impl DartOpaqueBase { + pub fn new(handle: Dart_PersistentHandle, drop_port: Option) -> Self { + Self { + inner: DartHandleWrap::from_raw(handle), + drop_port, + } + } + + pub fn into_raw(self) -> Dart_PersistentHandle { + self.inner.into_raw() + } + + pub fn unwrap(self) -> DartHandleWrap { + self.inner + } + + pub fn channel(&self) -> Option { + Some(Channel::new(self.drop_port?)) + } +} diff --git a/example/native/hub/src/bridge/bridge_engine/ffi/mod.rs b/example/native/hub/src/bridge/bridge_engine/ffi/mod.rs new file mode 100644 index 00000000..946856b7 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/ffi/mod.rs @@ -0,0 +1,262 @@ +#[cfg(target_family = "wasm")] +pub type DartAbi = wasm_bindgen::JsValue; +#[cfg(not(target_family = "wasm"))] +pub type DartAbi = allo_isolate::ffi::DartCObject; +#[cfg(not(target_family = "wasm"))] +use dart_sys::Dart_PersistentHandle; + +use std::{mem, ops, sync::Arc, thread::ThreadId}; + +#[cfg(not(target_family = "wasm"))] +pub use allo_isolate::IntoDart; + +#[cfg(target_family = "wasm")] +pub type MessagePort = web::PortLike; +#[cfg(not(target_family = "wasm"))] +pub type MessagePort = i64; + +#[cfg(target_family = "wasm")] +pub type OpaqueMessagePort = wasm_bindgen::JsValue; +#[cfg(not(target_family = "wasm"))] +pub type OpaqueMessagePort = i64; + +#[cfg(target_family = "wasm")] +pub type DartWrapObject = wasm_bindgen::JsValue; +#[cfg(not(target_family = "wasm"))] +pub type DartWrapObject = DartHandleWrap; + +#[cfg(target_family = "wasm")] +pub type DartObject = wasm_bindgen::JsValue; +#[cfg(not(target_family = "wasm"))] +pub type DartObject = Dart_PersistentHandle; + +#[cfg(target_family = "wasm")] +pub mod web; +#[cfg(target_family = "wasm")] +pub use web::*; + +#[cfg(not(target_family = "wasm"))] +pub type Channel = allo_isolate::Isolate; + +#[cfg(not(target_family = "wasm"))] +pub mod io; + +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +use crate::bridge::bridge_engine::DartSafe; + +/// A wrapper to transfer ownership of T to Dart. +/// +/// This type is equivalent to an [`Option>`]. The inner pointer may +/// be None if a nullptr is received from Dart, signifying that this pointer +/// has been disposed. +/// +/// Extensions for [`sync::RwLock`] and [`sync::Mutex`] are provided. +/// +/// ## Naming the inner type +/// +/// When an `RustOpaque` is transformed into a Dart type, T's string +/// representation undergoes some transformations to become a valid Dart type: +/// - Rust keywords (dyn, 'static, DartSafe, etc.) are automatically removed. +/// - ASCII alphanumerics are kept, all other characters are ignored. +/// +/// ## Trait objects +/// +/// Trait objects may be put behind opaque pointers, but they must implement +/// [`DartSafe`] to be safely sent to Dart. For example, this declaration can +/// be used across the FFI border: +/// +/// ```rust +/// use crate::*; +/// use std::fmt::Debug; +/// use std::panic::{UnwindSafe, RefUnwindSafe}; +/// +/// // Rust does not allow multiple non-auto traits in trait objects, so this +/// // is one workaround. +/// pub trait DartDebug: DartSafe + Debug {} +/// +/// impl DartDebug for T {} +/// +/// pub struct DebugWrapper(pub RustOpaque>); +/// +/// // creating a DebugWrapper using the opaque_dyn macro +/// let wrap = DebugWrapper(opaque_dyn!("foobar")); +/// // it's possible to name it directly +/// pub struct DebugWrapper2(pub RustOpaque>); +/// ``` +#[repr(transparent)] +#[derive(Debug)] +pub struct RustOpaque { + ptr: Option>, +} + +impl RustOpaque { + pub fn try_unwrap(self) -> Result { + if let Some(ptr) = self.ptr { + Arc::try_unwrap(ptr).map_err(RustOpaque::from) + } else { + panic!("Use after free.") + } + } +} + +impl Clone for RustOpaque { + fn clone(&self) -> Self { + Self { + ptr: self.ptr.clone(), + } + } +} + +/// # Safety +/// +/// This function should never be called manually. +/// Retrieving an opaque pointer from Dart is an implementation detail, so this +/// function is not guaranteed to be API-stable. +pub unsafe fn opaque_from_dart(ptr: *const T) -> RustOpaque { + // The raw pointer is the same one created from Arc::into_raw, + // owned and artificially incremented by Dart. + RustOpaque { + ptr: (!ptr.is_null()).then(|| Arc::from_raw(ptr)), + } +} + +impl ops::Deref for RustOpaque { + type Target = T; + + fn deref(&self) -> &Self::Target { + if let Some(ptr) = &self.ptr { + ptr.as_ref() + } else { + panic!("Use after free.") + } + } +} + +impl From> for RustOpaque { + fn from(ptr: Arc) -> Self { + Self { ptr: Some(ptr) } + } +} + +impl RustOpaque { + pub fn new(value: T) -> Self { + Self { + ptr: Some(Arc::new(value)), + } + } +} + +impl From> for DartAbi { + fn from(value: RustOpaque) -> Self { + let ptr = if let Some(ptr) = value.ptr { + Arc::into_raw(ptr) + } else { + std::ptr::null() + }; + let size = mem::size_of::(); + + vec![ptr.into_dart(), size.into_dart()].into_dart() + } +} + +#[derive(Debug)] +pub struct DartOpaque { + /// Dart object + handle: Option, + + /// The ID of the thread on which the Dart Object was created. + thread_id: ThreadId, +} + +/// # Safety +/// +/// The implementation checks the current thread +/// and delegates it to the Dart thread when it is drops. +unsafe impl Send for DartOpaque {} +unsafe impl Sync for DartOpaque {} + +impl DartOpaque { + /// Creates a new [DartOpaque]. + /// + /// # Safety + /// + /// The [DartObject] must be created on the current thread. + pub unsafe fn new(handle: DartObject, port: OpaqueMessagePort) -> Self { + Self { + handle: Some(DartOpaqueBase::new(handle, Some(port))), + thread_id: std::thread::current().id(), + } + } + + /// Creates a [DartOpaque] for sending to dart. + /// + /// # Safety + /// + /// The [DartObject] must be created on the current thread. + /// + /// The [DartOpaque] created by this method must not be dropped + /// on a non-parent [DartObject] thread. + pub unsafe fn new_non_droppable(handle: DartObject) -> Self { + Self { + handle: Some(DartOpaqueBase::new(handle, None)), + thread_id: std::thread::current().id(), + } + } + + /// Tries to get a Dart [DartObject]. + /// Returns the [DartObject] if the [DartOpaque] was created on the current thread. + pub fn try_unwrap(mut self) -> Result { + if std::thread::current().id() == self.thread_id { + Ok(self.handle.take().unwrap().unwrap()) + } else { + Err(self) + } + } +} + +impl From for DartAbi { + fn from(mut data: DartOpaque) -> Self { + data.handle.take().unwrap().into_raw().into_dart() + } +} + +impl Drop for DartOpaque { + fn drop(&mut self) { + if let Some(inner) = self.handle.take() { + if std::thread::current().id() != self.thread_id { + if let Some(channel) = inner.channel() { + let ptr = inner.into_raw(); + + if !channel.post(ptr) { + println!("Drop DartOpaque after closing the port."); + }; + } else { + println!("Drop non droppable DartOpaque."); + } + } + } + } +} + +/// Macro helper to instantiate an `RustOpaque`, as Rust does not +/// support custom DSTs on stable. +/// +/// Example: +/// ```rust +/// use std::fmt::Debug; +/// use crate::*; +/// +/// pub trait MyDebug: DartSafe + Debug {} +/// +/// impl MyDebug for T {} +/// +/// let opaque: RustOpaque> = opaque_dyn!("foobar"); +/// ``` +#[macro_export] +macro_rules! opaque_dyn { + ($ex:expr) => { + $crate::RustOpaque::new(::std::boxed::Box::new($ex)) + }; +} diff --git a/example/native/hub/src/bridge/bridge_engine/ffi/web.rs b/example/native/hub/src/bridge/bridge_engine/ffi/web.rs new file mode 100644 index 00000000..8f26cfa2 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/ffi/web.rs @@ -0,0 +1,409 @@ +use std::iter::FromIterator; + +use super::DartAbi; +use super::MessagePort; +use crate::bridge::bridge_engine::support; +pub use crate::bridge::bridge_engine::wasm_bindgen_src::transfer::*; +use crate::bridge::bridge_engine::DartOpaque; +use crate::bridge::bridge_engine::DartSafe; +use crate::bridge::bridge_engine::RustOpaque; +pub use js_sys; +pub use js_sys::Array as JsArray; +use js_sys::*; +pub use wasm_bindgen; +pub use wasm_bindgen::closure::Closure; +pub use wasm_bindgen::prelude::*; +pub use wasm_bindgen::JsCast; +use web_sys::BroadcastChannel; + +pub use crate::bridge::bridge_engine::wasm_bindgen_src::transfer::*; +pub trait IntoDart { + fn into_dart(self) -> DartAbi; +} + +pub trait IntoDartExceptPrimitive: IntoDart {} +impl IntoDartExceptPrimitive for JsValue {} +impl IntoDartExceptPrimitive for RustOpaque {} +impl IntoDartExceptPrimitive for DartOpaque {} +impl IntoDartExceptPrimitive for String {} +impl IntoDartExceptPrimitive for Option {} + +impl IntoDart for () { + #[inline] + fn into_dart(self) -> DartAbi { + JsValue::undefined() + } +} + +macro_rules! delegate { + ($( $ty:ty )*) => {$( + impl IntoDart for $ty { + #[inline] + fn into_dart(self) -> DartAbi { + DartAbi::from(self) + } + } + )*}; +} +macro_rules! delegate_buffer { + ($( $ty:ty => $buffer:ty )*) => {$( + impl IntoDart for $ty { + #[inline] + fn into_dart(self) -> DartAbi { + <$buffer>::from(self.as_slice()).into() + } + } + )*}; +} +// Orphan rules disallow blanket implementations, so we have to manually delegate here. +delegate! { + bool + i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 isize usize + f32 f64 + &str String JsValue +} +delegate_buffer! { + Vec => js_sys::Int8Array + Vec => js_sys::Uint8Array + Vec => js_sys::Int16Array + Vec => js_sys::Uint16Array + Vec => js_sys::Int32Array + Vec => js_sys::Uint32Array + Vec => js_sys::Float32Array + Vec => js_sys::Float64Array + ZeroCopyBuffer> => js_sys::Int8Array + ZeroCopyBuffer> => js_sys::Uint8Array + ZeroCopyBuffer> => js_sys::Int16Array + ZeroCopyBuffer> => js_sys::Uint16Array + ZeroCopyBuffer> => js_sys::Int32Array + ZeroCopyBuffer> => js_sys::Uint32Array + ZeroCopyBuffer> => js_sys::Float32Array + ZeroCopyBuffer> => js_sys::Float64Array +} + +impl IntoDart for Vec { + #[inline] + fn into_dart(self) -> DartAbi { + Array::from_iter(self.into_iter().map(IntoDart::into_dart)).into() + } +} + +impl IntoDart for Option { + #[inline] + fn into_dart(self) -> DartAbi { + self.map(T::into_dart).unwrap_or_else(JsValue::null) + } +} +impl IntoDart for *const T { + #[inline] + fn into_dart(self) -> DartAbi { + (self as usize).into_dart() + } +} +impl IntoDart for *mut T { + #[inline] + fn into_dart(self) -> DartAbi { + (self as usize).into_dart() + } +} + +impl IntoDart for RustOpaque { + #[inline] + fn into_dart(self) -> DartAbi { + self.into() + } +} + +impl IntoDart for DartOpaque { + #[inline] + fn into_dart(self) -> DartAbi { + self.into() + } +} + +impl IntoDart for [T; N] { + #[inline] + fn into_dart(self) -> DartAbi { + let boxed: Box<[T]> = Box::new(self); + boxed.into_vec().into_dart() + } +} + +macro_rules! impl_into_dart_for_primitive { + ($($prim:ty)*) => {$( + impl IntoDart for [$prim; N] { + #[inline] + fn into_dart(self) -> DartAbi { + Vec::from(self).into_dart() + } + } + )*}; +} + +impl_into_dart_for_primitive!(i8 u8 i16 u16 i32 u32 f32 f64); + +macro_rules! delegate_big_buffers { + ($($buf:ty => $buffer:ty)*) => {$( + impl IntoDart for $buf { + fn into_dart(self) -> DartAbi { + let buf: &[i32] = bytemuck::cast_slice(&self[..]); + let buf = Int32Array::from(buf); + <$buffer>::new(&buf.buffer()).into() + } + } + )*}; +} +delegate_big_buffers! { + Vec => BigInt64Array + Vec => BigUint64Array +} + +macro_rules! impl_into_dart_for_tuple { + ($( ($($T:ident)+) )*) => {$( + impl<$($T: IntoDart),+> IntoDart for ($($T),+,) { + #[allow(non_snake_case)] + fn into_dart(self) -> DartAbi { + let ($($T),+,) = self; + vec![$($T.into_dart()),+].into_dart() + } + } + + impl<$($T: IntoDart),+> IntoDartExceptPrimitive for ($($T),+,) {} + )*}; +} + +impl_into_dart_for_tuple! { + (A) + (A B) + (A B C) + (A B C D) + (A B C D E) + (A B C D E F) + (A B C D E F G) + (A B C D E F G H) + (A B C D E F G H I) + (A B C D E F G H I J) +} + +impl IntoDart for ZeroCopyBuffer> { + #[inline] + fn into_dart(self) -> DartAbi { + self.0.into_dart() + } +} +impl IntoDart for ZeroCopyBuffer> { + #[inline] + fn into_dart(self) -> DartAbi { + self.0.into_dart() + } +} + +#[derive(Clone)] +pub struct Channel { + port: MessagePort, +} + +impl Channel { + pub fn new(port: MessagePort) -> Self { + Self { port } + } + pub fn post(&self, msg: impl IntoDart) -> bool { + self.port + .post_message(&msg.into_dart()) + .map_err(|err| { + crate::console_error!("post: {:?}", err); + }) + .is_ok() + } + pub(crate) fn broadcast_name(&self) -> Option { + self.port + .dyn_ref::() + .map(|channel| channel.name()) + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console, js_name = "error")] + pub fn error(msg: &str); +} + +type RawClosure = Box; + +pub struct TransferClosure { + pub(crate) data: Vec, + pub(crate) transfer: Vec, + pub(crate) closure: RawClosure, +} + +pub struct TransferClosurePayload { + pub(crate) func: RawClosure, +} + +impl TransferClosure { + pub fn new( + data: Vec, + transfer: Vec, + closure: impl FnOnce(&[JsValue]) + Send + 'static, + ) -> Self { + let closure = Box::new(closure); + Self { + data, + transfer, + closure, + } + } +} + +#[derive(Debug)] +pub struct ZeroCopyBuffer(pub T); + +impl ZeroCopyBuffer> { + #[inline] + pub fn as_slice(&self) -> &[T] { + self.0.as_slice() + } +} + +/// Internal implementations for transferables on WASM platforms. +pub trait Transfer { + /// Recover the self value from a [JsValue]. + fn deserialize(value: &JsValue) -> Self; + /// Transform the self value into a [JsValue]. + fn serialize(self) -> JsValue; + /// Extract items that are valid to be passed as the "transfer" argument. + fn transferables(&self) -> Vec; +} + +impl Transfer for Option { + fn deserialize(value: &JsValue) -> Self { + (!value.is_undefined() && !value.is_null()).then(|| T::deserialize(value)) + } + fn serialize(self) -> JsValue { + self.map(T::serialize).unwrap_or_default() + } + fn transferables(&self) -> Vec { + self.as_ref().map(T::transferables).unwrap_or_default() + } +} + +impl Transfer for PortLike { + fn deserialize(value: &JsValue) -> Self { + if let Some(name) = value.as_string() { + BroadcastChannel::new(&name).unwrap().unchecked_into() + } else if value.dyn_ref::().is_some() { + value.unchecked_ref::().clone() + } else { + panic!("Not a PortLike: {:?}", value) + } + } + fn serialize(self) -> JsValue { + if let Some(channel) = self.dyn_ref::() { + channel.name().into() + } else { + self.into() + } + } + fn transferables(&self) -> Vec { + if let Some(port) = self.dyn_ref::() { + vec![port.clone().into()] + } else { + vec![] + } + } +} + +impl Transfer for ArrayBuffer { + fn deserialize(value: &JsValue) -> Self { + value.dyn_ref().cloned().unwrap() + } + fn serialize(self) -> JsValue { + self.into() + } + fn transferables(&self) -> Vec { + vec![self.into()] + } +} + +#[wasm_bindgen] +extern "C" { + /// Objects implementing the interface of [`web_sys::MessagePort`]. + /// + /// Attempts to coerce [`JsValue`]s into this interface using [`dyn_into`][JsCast::dyn_into] + /// or [`dyn_ref`][JsCast::dyn_ref] will fail at runtime. + #[derive(Clone)] + pub type PortLike; + #[wasm_bindgen(method, catch, js_name = "postMessage")] + pub fn post_message(this: &PortLike, value: &JsValue) -> Result<(), JsValue>; + #[wasm_bindgen(method, catch)] + pub fn close(this: &PortLike) -> Result<(), JsValue>; +} + +impl PortLike { + /// Create a [`BroadcastChannel`] with the specified name. + pub fn broadcast(name: &str) -> Self { + BroadcastChannel::new(name) + .expect("Failed to create broadcast channel") + .unchecked_into() + } +} + +/// Copied from https://github.com/chemicstry/wasm_thread/blob/main/src/script_path.js +pub fn script_path() -> Option { + js_sys::eval( + r" +(() => { + try { + throw new Error(); + } catch (e) { + let parts = e.stack.match(/(?:\(|@)(\S+):\d+:\d+/); + return parts[1]; + } +})()", + ) + .ok()? + .as_string() +} + +/// # Safety +/// +/// TODO: need doc +#[wasm_bindgen] +pub unsafe fn get_dart_object(ptr: usize) -> JsValue { + *support::box_from_leak_ptr(ptr as _) +} + +/// # Safety +/// +/// TODO: need doc +#[wasm_bindgen] +pub unsafe fn drop_dart_object(ptr: usize) { + drop(support::box_from_leak_ptr::(ptr as _)); +} + +#[derive(Debug)] +pub struct DartOpaqueBase { + inner: Box, + drop_port: Option, +} + +impl DartOpaqueBase { + pub fn new(handle: JsValue, port: Option) -> Self { + Self { + inner: Box::new(handle), + drop_port: port.map(|p| p.dyn_ref::().unwrap().name()), + } + } + + pub fn unwrap(self) -> JsValue { + *self.inner + } + + pub fn into_raw(self) -> *mut JsValue { + Box::into_raw(self.inner) + } + + pub fn channel(&self) -> Option { + Some(Channel::new(PortLike::broadcast(self.drop_port.as_ref()?))) + } +} diff --git a/example/native/hub/src/bridge/bridge_engine/handler.rs b/example/native/hub/src/bridge/bridge_engine/handler.rs new file mode 100644 index 00000000..201b1757 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/handler.rs @@ -0,0 +1,319 @@ +//! Wrappers and executors for Rust functions. + +use std::any::Any; +use std::panic; +use std::panic::{RefUnwindSafe, UnwindSafe}; + +use crate::bridge::bridge_engine::ffi::{IntoDart, MessagePort}; + +use crate::bridge::bridge_engine::rust2dart::{IntoIntoDart, Rust2Dart, TaskCallback}; +use crate::bridge::bridge_engine::support::WireSyncReturn; +use crate::bridge::bridge_engine::SyncReturn; +use crate::spawn_bridge_task; + +/// The types of return values for a particular Rust function. +#[derive(Copy, Clone)] +pub enum FfiCallMode { + /// The default mode, returns a Dart `Future`. + Normal, + /// Used by `SyncReturn` to skip spawning workers. + Sync, + /// Returns a Dart `Stream`. + Stream, +} + +/// Supporting information to identify a function's operating mode. +#[derive(Clone)] +pub struct WrapInfo { + /// A Dart `SendPort`. [None] if the mode is [FfiCallMode::Sync]. + pub port: Option, + /// Usually the name of the function. + pub debug_name: &'static str, + /// The call mode of this function. + pub mode: FfiCallMode, +} +/// Provide your own handler to customize how to execute your function calls, etc. +pub trait Handler { + /// Prepares the arguments, executes a Rust function and sets up its return value. + /// + /// Why separate `PrepareFn` and `TaskFn`: because some things cannot be [`Send`] (e.g. raw + /// pointers), so those can be done in `PrepareFn`, while the real work is done in `TaskFn` with [`Send`]. + /// + /// The generated code depends on the fact that `PrepareFn` is synchronous to maintain + /// correctness, therefore implementors of [`Handler`] must also uphold this property. + /// + /// If a Rust function returns [`SyncReturn`], it must be called with + /// [`wrap_sync`](Handler::wrap_sync) instead. + fn wrap(&self, wrap_info: WrapInfo, prepare: PrepareFn) + where + PrepareFn: FnOnce() -> TaskFn + UnwindSafe, + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart; + + /// Same as [`wrap`][Handler::wrap], but the Rust function must return a [SyncReturn] and + /// need not implement [Send]. + fn wrap_sync( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> WireSyncReturn + where + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + TaskRet: IntoDart; +} + +/// The simple handler uses a simple thread pool to execute tasks. +pub struct SimpleHandler { + executor: E, + error_handler: EH, +} + +impl SimpleHandler { + /// Create a new default handler. + pub fn new(executor: E, error_handler: H) -> Self { + SimpleHandler { + executor, + error_handler, + } + } +} + +/// The default handler used by the generated code. +pub type DefaultHandler = + SimpleHandler, ReportDartErrorHandler>; + +impl Default for DefaultHandler { + fn default() -> Self { + Self::new( + ThreadPoolExecutor::new(ReportDartErrorHandler), + ReportDartErrorHandler, + ) + } +} + +impl Handler for SimpleHandler { + fn wrap(&self, wrap_info: WrapInfo, prepare: PrepareFn) + where + PrepareFn: FnOnce() -> TaskFn + UnwindSafe, + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart, + { + // NOTE This extra [catch_unwind] **SHOULD** be put outside **ALL** code! + // Why do this: As nomicon says, unwind across languages is undefined behavior (UB). + // Therefore, we should wrap a [catch_unwind] outside of *each and every* line of code + // that can cause panic. Otherwise we may touch UB. + // Why do not report error or something like that if this outer [catch_unwind] really + // catches something: Because if we report error, that line of code itself can cause panic + // as well. Then that new panic will go across language boundary and cause UB. + // ref https://doc.rust-lang.org/nomicon/unwinding.html + let _ = panic::catch_unwind(move || { + let wrap_info2 = wrap_info.clone(); + if let Err(error) = panic::catch_unwind(move || { + let task = prepare(); + self.executor.execute(wrap_info2, task); + }) { + self.error_handler + .handle_error(wrap_info.port.unwrap(), BridgeError::Panic(error)); + } + }); + } + + fn wrap_sync( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> WireSyncReturn + where + TaskRet: IntoDart, + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + { + // NOTE This extra [catch_unwind] **SHOULD** be put outside **ALL** code! + // For reason, see comments in [wrap] + panic::catch_unwind(move || { + let catch_unwind_result = panic::catch_unwind(move || { + match self.executor.execute_sync(wrap_info, sync_task) { + Ok(data) => wire_sync_from_data(data.0, true), + Err(_err) => self + .error_handler + .handle_error_sync(BridgeError::ResultError), + } + }); + catch_unwind_result.unwrap_or_else(|error| { + self.error_handler + .handle_error_sync(BridgeError::Panic(error)) + }) + }) + .unwrap_or_else(|_| wire_sync_from_data(None::<()>, false)) + } +} + +/// An executor model for Rust functions. +/// +/// For example, the default model is [ThreadPoolExecutor] +/// which runs each function in a separate thread. +pub trait Executor: RefUnwindSafe { + /// Executes a Rust function and transforms its return value into a Dart-compatible + /// value, i.e. types that implement [`IntoDart`]. + fn execute(&self, wrap_info: WrapInfo, task: TaskFn) + where + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart; + + /// Executes a Rust function that returns a [SyncReturn]. + fn execute_sync( + &self, + wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> Result, BridgeError> + where + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + TaskRet: IntoDart; +} + +/// The default executor used. +/// It creates an internal thread pool, and each call to a Rust function is +/// handled by a different thread. +pub struct ThreadPoolExecutor { + error_handler: EH, +} + +impl ThreadPoolExecutor { + /// Create a new executor backed by a thread pool. + pub fn new(error_handler: EH) -> Self { + ThreadPoolExecutor { error_handler } + } +} + +impl Executor for ThreadPoolExecutor { + fn execute(&self, wrap_info: WrapInfo, task: TaskFn) + where + TaskFn: FnOnce(TaskCallback) -> Result + Send + UnwindSafe + 'static, + TaskRet: IntoIntoDart, + D: IntoDart, + { + let eh = self.error_handler; + let eh2 = self.error_handler; + + let WrapInfo { port, mode, .. } = wrap_info; + + spawn_bridge_task!(|port: Option| { + let port2 = port.as_ref().cloned(); + let thread_result = panic::catch_unwind(move || { + let port2 = port2.expect("(worker) thread"); + #[allow(clippy::clone_on_copy)] + let rust2dart = Rust2Dart::new(port2.clone()); + + let ret = task(TaskCallback::new(rust2dart.clone())) + .map(|e| e.into_into_dart().into_dart()); + + match ret { + Ok(result) => { + match mode { + FfiCallMode::Normal => { + rust2dart.success(result); + } + FfiCallMode::Stream => { + // nothing - ignore the return value of a Stream-typed function + } + FfiCallMode::Sync => { + panic!("FfiCallMode::Sync should not call execute, please call execute_sync instead") + } + } + } + Err(_error) => { + eh2.handle_error(port2, BridgeError::ResultError); + } + }; + }); + + if let Err(error) = thread_result { + eh.handle_error(port.expect("(worker) eh"), BridgeError::Panic(error)); + } + }); + } + + fn execute_sync( + &self, + _wrap_info: WrapInfo, + sync_task: SyncTaskFn, + ) -> Result, BridgeError> + where + SyncTaskFn: FnOnce() -> Result, BridgeError> + UnwindSafe, + TaskRet: IntoDart, + { + sync_task() + } +} + +/// Errors that occur from normal code execution. +#[derive(Debug)] +pub enum BridgeError { + ResultError, + /// Exceptional errors from panicking. + Panic(Box), +} + +impl BridgeError { + /// The identifier of the type of error. + pub fn code(&self) -> &'static str { + match self { + BridgeError::ResultError => "RESULT_ERROR", + BridgeError::Panic(_) => "PANIC_ERROR", + } + } + + /// The message of the error. + pub fn message(&self) -> String { + match self { + BridgeError::ResultError => "There was a result error inside the bridge".into(), + BridgeError::Panic(panic_err) => match panic_err.downcast_ref::<&'static str>() { + Some(s) => *s, + None => match panic_err.downcast_ref::() { + Some(s) => &s[..], + None => "Box", + }, + } + .to_string(), + } + } +} + +/// A handler model that sends back the error to a Dart `SendPort`. +/// +/// For example, instead of using the default [`ReportDartErrorHandler`], +/// you could implement your own handler that logs each error to stderr, +/// or to an external logging service. +pub trait ErrorHandler: UnwindSafe + RefUnwindSafe + Copy + Send + 'static { + /// The default error handler. + fn handle_error(&self, port: MessagePort, error: BridgeError); + + /// Special handler only used for synchronous code. + fn handle_error_sync(&self, error: BridgeError) -> WireSyncReturn; +} + +/// The default error handler used by generated code. +#[derive(Clone, Copy)] +pub struct ReportDartErrorHandler; + +impl ErrorHandler for ReportDartErrorHandler { + fn handle_error(&self, port: MessagePort, error: BridgeError) { + Rust2Dart::new(port).error(error.code().to_string(), error.message()); + } + + fn handle_error_sync(&self, error: BridgeError) -> WireSyncReturn { + wire_sync_from_data(format!("{}: {}", error.code(), error.message()), false) + } +} + +fn wire_sync_from_data(data: T, success: bool) -> WireSyncReturn { + let sync_return = vec![data.into_dart(), success.into_dart()].into_dart(); + + #[cfg(not(target_family = "wasm"))] + return crate::bridge::bridge_engine::support::new_leak_box_ptr(sync_return); + + #[cfg(target_family = "wasm")] + return sync_return; +} diff --git a/example/native/hub/src/bridge/bridge_engine/into_into_dart.rs b/example/native/hub/src/bridge/bridge_engine/into_into_dart.rs new file mode 100644 index 00000000..54a16143 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/into_into_dart.rs @@ -0,0 +1,179 @@ +use crate::bridge::bridge_engine::{ffi::*, DartSafe}; + +/// Basically the Into trait. +/// We need this separate trait because we need to implement it for Vec etc. +/// These blanket implementations allow us to accept external types in various places. +/// The initial reason for this was to allow mirrored types in StreamSink<>. +pub trait IntoIntoDart { + fn into_into_dart(self) -> D; +} + +impl IntoIntoDart> for Vec +where + T: IntoIntoDart, + Vec: IntoDart, + D: IntoDart, +{ + fn into_into_dart(self) -> Vec { + self.into_iter().map(|e| e.into_into_dart()).collect() + } +} + +impl IntoIntoDart> for Option +where + T: IntoIntoDart, + D: IntoDart, +{ + fn into_into_dart(self) -> Option { + self.map(|e| e.into_into_dart()) + } +} + +impl IntoIntoDart> for RustOpaque +where + T: DartSafe, +{ + fn into_into_dart(self) -> RustOpaque { + self + } +} + +impl IntoIntoDart> for ZeroCopyBuffer +where + T: IntoIntoDart, + D: IntoDart, + ZeroCopyBuffer: IntoDart, +{ + fn into_into_dart(self) -> ZeroCopyBuffer { + ZeroCopyBuffer(self.0.into_into_dart()) + } +} + +impl IntoIntoDart<[T; C]> for [T; C] +where + T: IntoDart, + [T; C]: IntoDart, +{ + fn into_into_dart(self) -> [T; C] { + self + } +} + +impl IntoIntoDart for Box +where + T: IntoDart, +{ + fn into_into_dart(self) -> T { + *self + } +} + +// These tuple impls should probably be a macro, +// but that is not easily possible with macro_rules because of the field access. +impl IntoIntoDart<(AD, BD)> for (A, B) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, +{ + fn into_into_dart(self) -> (AD, BD) { + (self.0.into_into_dart(), self.1.into_into_dart()) + } +} +impl IntoIntoDart<(AD, BD, CD)> for (A, B, C) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + CD: IntoDart, +{ + fn into_into_dart(self) -> (AD, BD, CD) { + ( + self.0.into_into_dart(), + self.1.into_into_dart(), + self.2.into_into_dart(), + ) + } +} +impl IntoIntoDart<(AD, BD, CD, DD)> for (A, B, C, D) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + CD: IntoDart, + D: IntoIntoDart
, + DD: IntoDart, +{ + fn into_into_dart(self) -> (AD, BD, CD, DD) { + ( + self.0.into_into_dart(), + self.1.into_into_dart(), + self.2.into_into_dart(), + self.3.into_into_dart(), + ) + } +} +impl IntoIntoDart<(AD, BD, CD, DD, ED)> for (A, B, C, D, E) +where + A: IntoIntoDart, + AD: IntoDart, + B: IntoIntoDart, + BD: IntoDart, + C: IntoIntoDart, + CD: IntoDart, + D: IntoIntoDart
, + DD: IntoDart, + E: IntoIntoDart, + ED: IntoDart, +{ + fn into_into_dart(self) -> (AD, BD, CD, DD, ED) { + ( + self.0.into_into_dart(), + self.1.into_into_dart(), + self.2.into_into_dart(), + self.3.into_into_dart(), + self.4.into_into_dart(), + ) + } +} + +// more generic impls do not work because they crate possibly conflicting trait impls +// this is why here are some more specific impls + +// Implementations for simple types +macro_rules! impl_into_into_dart { + ($t:ty) => { + impl IntoIntoDart<$t> for $t { + fn into_into_dart(self) -> $t { + self + } + } + }; +} + +// Impls for primitive types are taken from the IntoDart trait + +impl_into_into_dart!(u8); +impl_into_into_dart!(i8); +impl_into_into_dart!(u16); +impl_into_into_dart!(i16); +impl_into_into_dart!(u32); +impl_into_into_dart!(i32); +impl_into_into_dart!(u64); +impl_into_into_dart!(i64); +impl_into_into_dart!(f32); +impl_into_into_dart!(f64); +impl_into_into_dart!(bool); +impl_into_into_dart!(()); +impl_into_into_dart!(usize); +impl_into_into_dart!(String); +impl_into_into_dart!(DartOpaque); +#[cfg(not(target_family = "wasm"))] +impl_into_into_dart!(allo_isolate::ffi::DartCObject); +#[cfg(target_family = "wasm")] +impl_into_into_dart!(wasm_bindgen::JsValue); diff --git a/example/native/hub/src/bridge/bridge_engine/macros.rs b/example/native/hub/src/bridge/bridge_engine/macros.rs new file mode 100644 index 00000000..955c3dbd --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/macros.rs @@ -0,0 +1,75 @@ +#[macro_export] +macro_rules! spawn_bridge_task { + ($($tt:tt)*) => {{ + let bridge_task = $crate::transfer!($($tt)*); + #[cfg(not(target_family = "wasm"))] + { + bridge_task(); + } + #[cfg(target_family = "wasm")] + { + $crate::bridge::bridge_engine::wasm_bindgen_src::worker::WEB_WORKER.with(|inner:&std::cell::RefCell>| { + let borrowed = inner.borrow(); + let web_worker = borrowed.as_ref().unwrap(); + let _ = bridge_task.apply(web_worker); + }); + } + }}; +} + +/// On WASM, [JsValue][wasm_bindgen::JsValue]s cannot be shared between scopes +/// but instead can be ["transferred"]. +/// Rust however is not aware of transferables and therefore cannot capture these values. +/// This macro wraps a closure and returns a [TransferClosure][crate::ffi::TransferClosure] +/// on WASM platforms which will capture these special values, +/// or a normal [FnOnce] on other platforms. +/// Note that the parameter names must match available variables/bindings from the outer scope. +/// +/// ["transferred"]: https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects +#[macro_export] +macro_rules! transfer { + (|| $block:block) => {{ + #[cfg(not(target_family = "wasm"))] + { move || $block } + #[cfg(target_family = "wasm")] + { + $crate::ffi::TransferClosure::new(vec![], vec![], move |_: &[JsValue]| $block) + } + }}; + (|$($param:ident: $ty:ty),*| $block:block) => {{ + #[cfg(not(target_family = "wasm"))] + { + move || $block + } + #[cfg(target_family = "wasm")] + { + use wasm_bindgen::JsValue; + use $crate::bridge::bridge_engine::ffi::Transfer; + #[allow(unused_variables)] + let worker = move |transfer: &[JsValue]| { + let idx = 0; + $( + let $param = <$ty>::deserialize(&transfer[idx]); + let idx = idx + 1; + )* + $block + }; + let transferables = [$($param.transferables()),*].concat(); + $crate::bridge::bridge_engine::ffi::TransferClosure::new( + vec![$($param.serialize()),*], + transferables, + worker, + ) + } + }}; +} + +#[macro_export] +macro_rules! console_error { + ($lit:literal) => { + $crate::error($lit) + }; + ($($tt:tt)*) => { + $crate::bridge::bridge_engine::error(&format!($($tt)*)) + }; +} diff --git a/example/native/hub/src/bridge/bridge_engine/mod.rs b/example/native/hub/src/bridge/bridge_engine/mod.rs new file mode 100644 index 00000000..cb66f3d2 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/mod.rs @@ -0,0 +1,24 @@ +use std::panic::{RefUnwindSafe, UnwindSafe}; + +pub use handler::{FfiCallMode, Handler, WrapInfo}; +pub use rust2dart::StreamSink; +pub mod ffi; +pub use ffi::*; +pub mod handler; +mod into_into_dart; +mod macros; +pub mod rust2dart; +pub mod support; + +#[cfg(target_family = "wasm")] +pub mod wasm_bindgen_src; + +/// Use this struct in return type of your function, in order to tell the code generator +/// the function should return synchronously. Otherwise, it is by default asynchronously. +pub struct SyncReturn(pub T); + +/// Marker trait for types that are safe to share with Dart and can be dropped +/// safely in case of a panic. +pub trait DartSafe: UnwindSafe + RefUnwindSafe {} + +impl DartSafe for T {} diff --git a/example/native/hub/src/bridge/bridge_engine/rust2dart.rs b/example/native/hub/src/bridge/bridge_engine/rust2dart.rs new file mode 100644 index 00000000..8e1bc6cd --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/rust2dart.rs @@ -0,0 +1,150 @@ +//! Manages receiving and sending values across the FFI boundary. + +use std::marker::PhantomData; + +/// The representation of a Dart object outside of the Dart heap. +/// +/// Its implementation lies with the Dart language and therefore should not be +/// depended on to be stable. +pub use crate::bridge::bridge_engine::ffi::*; +pub use crate::bridge::bridge_engine::into_into_dart::IntoIntoDart; + +/// A wrapper around a Dart [`Isolate`]. +#[derive(Clone)] +pub struct Rust2Dart { + pub(crate) channel: Channel, +} + +const RUST2DART_ACTION_SUCCESS: i32 = 0; +const RUST2DART_ACTION_ERROR: i32 = 1; +const RUST2DART_ACTION_CLOSE_STREAM: i32 = 2; + +// API signatures is similar to Flutter Android's callback +// https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodChannel.Result.html +impl Rust2Dart { + /// Create a new wrapper from a raw port. + pub fn new(port: MessagePort) -> Self { + Rust2Dart { + channel: Channel::new(port), + } + } + + /// Send a success message back to the specified port. + pub fn success(&self, result: impl IntoDart) -> bool { + self.channel.post(vec![ + RUST2DART_ACTION_SUCCESS.into_dart(), + result.into_dart(), + ]) + } + + /// Send an error back to the specified port. + pub fn error(&self, error_code: String, error_message: String) -> bool { + self.error_full(error_code, error_message, ()) + } + + /// Send a detailed error back to the specified port. + pub fn error_full( + &self, + error_code: String, + error_message: String, + error_details: impl IntoDart, + ) -> bool { + self.channel.post(vec![ + RUST2DART_ACTION_ERROR.into_dart(), + error_code.into_dart(), + error_message.into_dart(), + error_details.into_dart(), + ]) + } + + /// Close the stream and ignore further messages. + pub fn close_stream(&self) -> bool { + self.channel + .post(vec![RUST2DART_ACTION_CLOSE_STREAM.into_dart()]) + } +} + +/// A callback that receives the return value of Rust functions. +pub struct TaskCallback { + rust2dart: Rust2Dart, +} + +impl TaskCallback { + /// Create a new callback from a port wrapper. + pub fn new(rust2dart: Rust2Dart) -> Self { + Self { rust2dart } + } + + /// Create a new [StreamSink] of the specified type. + pub fn stream_sink(&self) -> StreamSink + where + T: IntoIntoDart, + D: IntoDart, + { + StreamSink::new(self.rust2dart.clone()) + } +} + +/// A handle to a [`web_sys::BroadcastChannel`]. +#[derive(Clone)] +pub struct ChannelHandle(pub String); + +impl ChannelHandle { + #[cfg(target_family = "wasm")] + pub fn port(&self) -> MessagePort { + PortLike::broadcast(&self.0) + } +} + +/// A sink to send asynchronous data back to Dart. +/// Represented as a Dart +/// [`Stream`](https://api.dart.dev/stable/dart-async/Stream-class.html). +#[derive(Clone)] +pub struct StreamSink { + #[cfg(not(target_family = "wasm"))] + rust2dart: Rust2Dart, + #[cfg(target_family = "wasm")] + handle: ChannelHandle, + _phantom_data: PhantomData, +} + +impl StreamSink { + /// Create a new sink from a port wrapper. + pub fn new(rust2dart: Rust2Dart) -> Self { + #[cfg(target_family = "wasm")] + let name = rust2dart + .channel + .broadcast_name() + .expect("Not a BroadcastChannel"); + Self { + #[cfg(not(target_family = "wasm"))] + rust2dart, + #[cfg(target_family = "wasm")] + handle: ChannelHandle(name), + _phantom_data: PhantomData, + } + } + + fn rust2dart(&self) -> Rust2Dart { + #[cfg(not(target_family = "wasm"))] + return self.rust2dart.clone(); + + #[cfg(target_family = "wasm")] + Rust2Dart::new(self.handle.port()) + } + + /// Add data to the stream. Returns false when data could not be sent, + /// or the stream has been closed. + pub fn add(&self, value: T) -> bool + where + T: IntoIntoDart, + { + self.rust2dart().success(value.into_into_dart().into_dart()) + } + + /// Close the stream and ignore further messages. Returns false when + /// the stream could not be closed, or when it has already been closed. + pub fn close(&self) -> bool { + self.rust2dart().close_stream() + } +} diff --git a/example/native/hub/src/bridge/bridge_engine/support.rs b/example/native/hub/src/bridge/bridge_engine/support.rs new file mode 100644 index 00000000..53c2b2e0 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/support.rs @@ -0,0 +1,75 @@ +//! Functions that support auto-generated Rust code. +//! These functions are *not* meant to be used by humans directly. +#![doc(hidden)] + +use std::mem; + +pub use crate::bridge::bridge_engine::ffi::*; +pub use lazy_static::lazy_static; + +pub use crate::bridge::bridge_engine::handler::DefaultHandler; + +// ref https://stackoverflow.com/questions/39224904/how-to-expose-a-rust-vect-to-ffi +pub fn new_leak_vec_ptr(fill: T, length: i32) -> *mut T { + into_leak_vec_ptr(vec![fill; length as usize]).0 +} + +pub fn into_leak_vec_ptr(mut v: Vec) -> (*mut T, i32) { + v.shrink_to_fit(); + assert!(v.len() == v.capacity()); + let ptr = v.as_mut_ptr(); + let len = v.len() as i32; + mem::forget(v); + (ptr, len) +} + +/// # Safety +/// Use it in pair with [new_leak_vec_ptr]. +pub unsafe fn vec_from_leak_ptr(ptr: *mut T, len: i32) -> Vec { + Vec::from_raw_parts(ptr, len as usize, len as usize) +} + +/// Convert [Vec] to array length `N`. +/// +/// # Panics +/// +/// Panics if length of [Vec] != `N`. +pub fn from_vec_to_array(v: Vec) -> [T; N] { + core::convert::TryInto::try_into(v) + .unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())) +} + +// ref: doc of [Box::into_raw] +pub fn new_leak_box_ptr(t: T) -> *mut T { + let x: Box = Box::new(t); + Box::into_raw(x) +} + +/// # Safety +/// Use it in pair with [new_leak_box_ptr]. +pub unsafe fn box_from_leak_ptr(ptr: *mut T) -> Box { + Box::from_raw(ptr) +} + +/// Cast a byte buffer into a boxed slice of the target type without making any copies. +/// Panics if the cast fails. +pub fn slice_from_byte_buffer(buffer: Vec) -> Box<[T]> { + let buf = Box::leak(buffer.into_boxed_slice()); + match bytemuck::try_cast_slice_mut(buf) { + Ok(buf) => unsafe { Box::from_raw(buf) }, + Err(err) => { + // clean up before panicking + unsafe { core::ptr::drop_in_place(buf) } + panic!("cast error: {}", err); + } + } +} + +#[cfg(not(target_family = "wasm"))] +use allo_isolate::ffi::DartCObject; + +#[cfg(not(target_family = "wasm"))] +pub type WireSyncReturn = *mut DartCObject; + +#[cfg(target_family = "wasm")] +pub type WireSyncReturn = wasm_bindgen::JsValue; diff --git a/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs new file mode 100644 index 00000000..7a2cc6df --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/mod.rs @@ -0,0 +1,4 @@ +//! Code originally sourced from wasm-bindgen's repository. + +pub mod transfer; +pub mod worker; diff --git a/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs new file mode 100644 index 00000000..66ceed8b --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/transfer.rs @@ -0,0 +1,46 @@ +use crate::bridge::bridge_engine::ffi::web::*; +use js_sys::{global, Array}; +use std::iter::FromIterator; +use web_sys::{DedicatedWorkerGlobalScope, Worker}; + +impl TransferClosure { + /// Posts a [*mut [TransferClosurePayload], ...[JsValue]] message to this worker. + /// + /// The worker's `onmessage` should run the corresponding [`receive_transfer_closure`] + /// to receive the message. + pub fn apply(self, worker: &Worker) -> Result<(), JsValue> { + let transfer = self.transfer.into_iter().filter(|value| value.is_truthy()); + let transfer = Array::from_iter(transfer); + let data = Array::from_iter(self.data); + // The worker is responsible for cleaning up the leak here. + let payload = Box::into_raw(Box::new(TransferClosurePayload { func: self.closure })); + data.unshift(&JsValue::from(payload as i32)); + worker + .post_message_with_transfer(&data, &transfer) + .map_err(|err| { + // post message failed, ownership remains with us. + drop(unsafe { Box::from_raw(payload) }); + err + }) + } +} + +/// ## Safety +/// This function reclaims a raw pointer created by [`TransferClosure`], and therefore +/// should **only** be used in conjunction with it. +/// Furthermore, the WASM module in the worker must have been initialized with the shared +/// memory from the host JS scope. +// wasm_bindgen cannot work with unsafe functions, hence the clippy ignore. +#[wasm_bindgen] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn receive_transfer_closure( + payload: *mut TransferClosurePayload, + transfer: Box<[JsValue]>, +) -> Result<(), JsValue> { + let closure = unsafe { Box::from_raw(payload) }; + (closure.func)(&transfer); + // Once we're done here, notify the host scope so that it can reclaim this worker. + global() + .unchecked_into::() + .post_message(&JsValue::UNDEFINED) +} diff --git a/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs new file mode 100644 index 00000000..e98656e4 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_engine/wasm_bindgen_src/worker.rs @@ -0,0 +1,60 @@ +use crate::bridge::bridge_engine::script_path; +use js_sys::Array; +use std::cell::RefCell; +use wasm_bindgen::prelude::*; +use web_sys::{Blob, BlobPropertyBag, Url, Worker}; + +thread_local! { + pub static WEB_WORKER: RefCell> = RefCell::new(Some(create_worker())); +} + +pub fn replace_worker() { + WEB_WORKER.with(|inner| { + let popped = inner.replace(None); + if let Some(previous_worker) = popped { + previous_worker.terminate(); + } + let new_worker = create_worker(); + inner.replace(Some(new_worker)); + }) +} + +fn create_worker() -> Worker { + let script = format!( + "importScripts('{}'); + onmessage = event => {{ + let init = wasm_bindgen(...event.data).catch(err => {{ + setTimeout(() => {{ throw err }}) + throw err + }}) + onmessage = async event => {{ + await init + let [payload, ...transfer] = event.data + try {{ + wasm_bindgen.receive_transfer_closure(payload, transfer) + }} catch (err) {{ + if (transfer[0] && typeof transfer[0].postMessage === 'function') {{ + transfer[0].postMessage([1, 'ABORT', err.toString(), err.stack]) + }} + setTimeout(() => {{ throw err }}) + postMessage(null) + throw err + }} + }} + }}", + script_path().unwrap() + ); + let blob = Blob::new_with_blob_sequence_and_options( + &Array::from_iter([JsValue::from(script)]).into(), + BlobPropertyBag::new().type_("text/javascript"), + ) + .unwrap(); + let url = Url::create_object_url_with_blob(&blob).unwrap(); + let worker = Worker::new(&url).unwrap(); + let module = wasm_bindgen::module(); + let memory = wasm_bindgen::memory(); + worker + .post_message(&Array::from_iter([module, memory])) + .unwrap(); + worker +} diff --git a/example/native/hub/src/bridge/bridge_generated.io.rs b/example/native/hub/src/bridge/bridge_generated.io.rs new file mode 100644 index 00000000..37fca4d0 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_generated.io.rs @@ -0,0 +1,164 @@ +use super::*; +// Section: wire functions + +#[no_mangle] +pub extern "C" fn wire_prepare_rust_signal_stream(port_: i64) { + wire_prepare_rust_signal_stream_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_prepare_rust_response_stream(port_: i64) { + wire_prepare_rust_response_stream_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_prepare_channels(port_: i64) { + wire_prepare_channels_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_check_rust_streams(port_: i64) { + wire_check_rust_streams_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_start_rust_logic(port_: i64) { + wire_start_rust_logic_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_request_to_rust(port_: i64, request_unique: *mut wire_RustRequestUnique) { + wire_request_to_rust_impl(port_, request_unique) +} + +// Section: allocate functions + +#[no_mangle] +pub extern "C" fn new_box_autoadd_rust_request_unique_0() -> *mut wire_RustRequestUnique { + support::new_leak_box_ptr(wire_RustRequestUnique::new_with_null_ptr()) +} + +#[no_mangle] +pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { + let ans = wire_uint_8_list { + ptr: support::new_leak_vec_ptr(Default::default(), len), + len, + }; + support::new_leak_box_ptr(ans) +} + +// Section: related functions + +// Section: impl Wire2Api + +impl Wire2Api for *mut wire_RustRequestUnique { + fn wire2api(self) -> RustRequestUnique { + let wrap = unsafe { support::box_from_leak_ptr(self) }; + Wire2Api::::wire2api(*wrap).into() + } +} + +impl Wire2Api for wire_RustRequest { + fn wire2api(self) -> RustRequest { + RustRequest { + resource: self.resource.wire2api(), + operation: self.operation.wire2api(), + message: self.message.wire2api(), + blob: self.blob.wire2api(), + } + } +} +impl Wire2Api for wire_RustRequestUnique { + fn wire2api(self) -> RustRequestUnique { + RustRequestUnique { + id: self.id.wire2api(), + request: self.request.wire2api(), + } + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} +// Section: wire structs + +#[repr(C)] +#[derive(Clone)] +pub struct wire_RustRequest { + resource: i32, + operation: i32, + message: *mut wire_uint_8_list, + blob: *mut wire_uint_8_list, +} + +#[repr(C)] +#[derive(Clone)] +pub struct wire_RustRequestUnique { + id: i32, + request: wire_RustRequest, +} + +#[repr(C)] +#[derive(Clone)] +pub struct wire_uint_8_list { + ptr: *mut u8, + len: i32, +} + +// Section: impl NewWithNullPtr + +pub trait NewWithNullPtr { + fn new_with_null_ptr() -> Self; +} + +impl NewWithNullPtr for *mut T { + fn new_with_null_ptr() -> Self { + std::ptr::null_mut() + } +} + +impl NewWithNullPtr for wire_RustRequest { + fn new_with_null_ptr() -> Self { + Self { + resource: Default::default(), + operation: Default::default(), + message: core::ptr::null_mut(), + blob: core::ptr::null_mut(), + } + } +} + +impl Default for wire_RustRequest { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + +impl NewWithNullPtr for wire_RustRequestUnique { + fn new_with_null_ptr() -> Self { + Self { + id: Default::default(), + request: Default::default(), + } + } +} + +impl Default for wire_RustRequestUnique { + fn default() -> Self { + Self::new_with_null_ptr() + } +} + +// Section: sync execution mode utility + +#[no_mangle] +pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { + unsafe { + let _ = support::box_from_leak_ptr(ptr); + }; +} diff --git a/example/native/hub/src/bridge/bridge_generated.rs b/example/native/hub/src/bridge/bridge_generated.rs new file mode 100644 index 00000000..5c0afc40 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_generated.rs @@ -0,0 +1,219 @@ +#![allow( + non_camel_case_types, + unused, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::double_parens, + non_snake_case, + clippy::too_many_arguments +)] +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by flutter_rust_bridge_codegen@ 1.81.0. + +use crate::bridge::api::*; +use crate::bridge::bridge_engine::rust2dart::IntoIntoDart; +use crate::bridge::bridge_engine::*; +use core::panic::UnwindSafe; +use std::ffi::c_void; +use std::sync::Arc; + +// Section: imports + +// Section: wire functions + +fn wire_prepare_rust_signal_stream_impl(port_: MessagePort) { + BRIDGE_HANDLER.wrap::<_, _, _, ()>( + WrapInfo { + debug_name: "prepare_rust_signal_stream", + port: Some(port_), + mode: FfiCallMode::Stream, + }, + move || { + move |task_callback| { + Ok(prepare_rust_signal_stream( + task_callback.stream_sink::<_, RustSignal>(), + )) + } + }, + ) +} +fn wire_prepare_rust_response_stream_impl(port_: MessagePort) { + BRIDGE_HANDLER.wrap::<_, _, _, ()>( + WrapInfo { + debug_name: "prepare_rust_response_stream", + port: Some(port_), + mode: FfiCallMode::Stream, + }, + move || { + move |task_callback| { + Ok(prepare_rust_response_stream( + task_callback.stream_sink::<_, RustResponseUnique>(), + )) + } + }, + ) +} +fn wire_prepare_channels_impl(port_: MessagePort) { + BRIDGE_HANDLER.wrap::<_, _, _, ()>( + WrapInfo { + debug_name: "prepare_channels", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(prepare_channels()), + ) +} +fn wire_check_rust_streams_impl(port_: MessagePort) { + BRIDGE_HANDLER.wrap::<_, _, _, bool>( + WrapInfo { + debug_name: "check_rust_streams", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(check_rust_streams()), + ) +} +fn wire_start_rust_logic_impl(port_: MessagePort) { + BRIDGE_HANDLER.wrap::<_, _, _, ()>( + WrapInfo { + debug_name: "start_rust_logic", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(start_rust_logic()), + ) +} +fn wire_request_to_rust_impl( + port_: MessagePort, + request_unique: impl Wire2Api + UnwindSafe, +) { + BRIDGE_HANDLER.wrap::<_, _, _, ()>( + WrapInfo { + debug_name: "request_to_rust", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || { + let api_request_unique = request_unique.wire2api(); + move |task_callback| Ok(request_to_rust(api_request_unique)) + }, + ) +} +// Section: wrapper structs + +// Section: static checks + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +pub trait Wire2Api { + fn wire2api(self) -> T; +} + +impl Wire2Api> for *mut S +where + *mut S: Wire2Api, +{ + fn wire2api(self) -> Option { + (!self.is_null()).then(|| self.wire2api()) + } +} + +impl Wire2Api for i32 { + fn wire2api(self) -> i32 { + self + } +} + +impl Wire2Api for i32 { + fn wire2api(self) -> RustOperation { + match self { + 0 => RustOperation::Create, + 1 => RustOperation::Read, + 2 => RustOperation::Update, + 3 => RustOperation::Delete, + _ => unreachable!("Invalid variant for RustOperation: {}", self), + } + } +} + +impl Wire2Api for u8 { + fn wire2api(self) -> u8 { + self + } +} + +// Section: impl IntoDart + +impl support::IntoDart for RustResponse { + fn into_dart(self) -> support::DartAbi { + vec![ + self.successful.into_into_dart().into_dart(), + self.message.into_dart(), + self.blob.into_dart(), + ] + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for RustResponse {} +impl rust2dart::IntoIntoDart for RustResponse { + fn into_into_dart(self) -> Self { + self + } +} + +impl support::IntoDart for RustResponseUnique { + fn into_dart(self) -> support::DartAbi { + vec![ + self.id.into_into_dart().into_dart(), + self.response.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for RustResponseUnique {} +impl rust2dart::IntoIntoDart for RustResponseUnique { + fn into_into_dart(self) -> Self { + self + } +} + +impl support::IntoDart for RustSignal { + fn into_dart(self) -> support::DartAbi { + vec![ + self.resource.into_into_dart().into_dart(), + self.message.into_dart(), + self.blob.into_dart(), + ] + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for RustSignal {} +impl rust2dart::IntoIntoDart for RustSignal { + fn into_into_dart(self) -> Self { + self + } +} + +// Section: executor + +support::lazy_static! { + pub static ref BRIDGE_HANDLER: support::DefaultHandler = Default::default(); +} + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +#[path = "bridge_generated.web.rs"] +mod web; +#[cfg(target_family = "wasm")] +pub use web::*; + +#[cfg(not(target_family = "wasm"))] +#[path = "bridge_generated.io.rs"] +mod io; +#[cfg(not(target_family = "wasm"))] +pub use io::*; diff --git a/example/native/hub/src/bridge/bridge_generated.web.rs b/example/native/hub/src/bridge/bridge_generated.web.rs new file mode 100644 index 00000000..e63e79c4 --- /dev/null +++ b/example/native/hub/src/bridge/bridge_generated.web.rs @@ -0,0 +1,110 @@ +use super::*; +// Section: wire functions + +#[wasm_bindgen] +pub fn wire_prepare_rust_signal_stream(port_: MessagePort) { + wire_prepare_rust_signal_stream_impl(port_) +} + +#[wasm_bindgen] +pub fn wire_prepare_rust_response_stream(port_: MessagePort) { + wire_prepare_rust_response_stream_impl(port_) +} + +#[wasm_bindgen] +pub fn wire_prepare_channels(port_: MessagePort) { + wire_prepare_channels_impl(port_) +} + +#[wasm_bindgen] +pub fn wire_check_rust_streams(port_: MessagePort) { + wire_check_rust_streams_impl(port_) +} + +#[wasm_bindgen] +pub fn wire_start_rust_logic(port_: MessagePort) { + wire_start_rust_logic_impl(port_) +} + +#[wasm_bindgen] +pub fn wire_request_to_rust(port_: MessagePort, request_unique: JsValue) { + wire_request_to_rust_impl(port_, request_unique) +} + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +impl Wire2Api>> for Option> { + fn wire2api(self) -> Option> { + self.map(Wire2Api::wire2api) + } +} + +impl Wire2Api for JsValue { + fn wire2api(self) -> RustRequest { + let self_ = self.dyn_into::().unwrap(); + assert_eq!( + self_.length(), + 4, + "Expected 4 elements, got {}", + self_.length() + ); + RustRequest { + resource: self_.get(0).wire2api(), + operation: self_.get(1).wire2api(), + message: self_.get(2).wire2api(), + blob: self_.get(3).wire2api(), + } + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> RustRequestUnique { + let self_ = self.dyn_into::().unwrap(); + assert_eq!( + self_.length(), + 2, + "Expected 2 elements, got {}", + self_.length() + ); + RustRequestUnique { + id: self_.get(0).wire2api(), + request: self_.get(1).wire2api(), + } + } +} + +impl Wire2Api> for Box<[u8]> { + fn wire2api(self) -> Vec { + self.into_vec() + } +} +// Section: impl Wire2Api for JsValue + +impl Wire2Api for JsValue { + fn wire2api(self) -> i32 { + self.unchecked_into_f64() as _ + } +} +impl Wire2Api>> for JsValue { + fn wire2api(self) -> Option> { + (!self.is_undefined() && !self.is_null()).then(|| self.wire2api()) + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> RustOperation { + (self.unchecked_into_f64() as i32).wire2api() + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> u8 { + self.unchecked_into_f64() as _ + } +} +impl Wire2Api> for JsValue { + fn wire2api(self) -> Vec { + self.unchecked_into::().to_vec().into() + } +} diff --git a/example/native/hub/src/bridge/mod.rs b/example/native/hub/src/bridge/mod.rs new file mode 100644 index 00000000..47ad80ad --- /dev/null +++ b/example/native/hub/src/bridge/mod.rs @@ -0,0 +1,57 @@ +//! This module supports communication with Dart. +//! More specifically, sending responses and +//! stream signals to Dart are supported. +//! DO NOT EDIT. +#![allow(dead_code)] + +use api::RustResponseUnique; +use api::RustSignal; +use tokio::sync::mpsc::Receiver; + +pub mod api; +pub mod bridge_engine; +mod bridge_generated; + +/// This function is expected to be used only once +/// during the initialization of the Rust logic. +pub fn get_request_receiver() -> Receiver { + let cell = api::REQUST_RECEIVER_SHARED.lock().unwrap(); + let option = cell.replace(None); + option.unwrap() +} + +/// Sending the signal will notify the Flutter widgets +/// and trigger the rebuild. +/// No memory copy is involved as the bytes are moved directly to Dart. +pub fn send_rust_signal(rust_signal: RustSignal) { + api::SIGNAL_STREAM.with(|inner| { + let mut borrowed = inner.borrow_mut(); + let option = borrowed.as_ref(); + if let Some(stream) = option { + stream.add(rust_signal); + } else { + let cell = api::SIGNAL_STREAM_SHARED.lock().unwrap(); + let stream = cell.borrow().as_ref().unwrap().clone(); + stream.add(rust_signal); + borrowed.replace(stream); + } + }); +} + +/// Send a response to Dart with a unique interaction ID +/// to remember which request that response corresponds to. +/// No memory copy is involved as the bytes are moved directly to Dart. +pub fn respond_to_dart(response_unique: RustResponseUnique) { + api::RESPONSE_STREAM.with(|inner| { + let mut borrowed = inner.borrow_mut(); + let option = borrowed.as_ref(); + if let Some(stream) = option { + stream.add(response_unique); + } else { + let cell = api::RESPONSE_STREAM_SHARED.lock().unwrap(); + let stream = cell.borrow().as_ref().unwrap().clone(); + stream.add(response_unique); + borrowed.replace(stream); + } + }); +} diff --git a/example/native/hub/src/lib.rs b/example/native/hub/src/lib.rs new file mode 100644 index 00000000..9d267dbd --- /dev/null +++ b/example/native/hub/src/lib.rs @@ -0,0 +1,25 @@ +use bridge::respond_to_dart; +use web_alias::*; +use with_request::handle_request; + +mod bridge; +mod messages; +mod sample_functions; +mod web_alias; +mod with_request; + +/// This `hub` crate is the entry point for the Rust logic. +/// Always use non-blocking async functions such as `tokio::fs::File::open`. +async fn main() { + // This is `tokio::sync::mpsc::Reciver` that receives the requests from Dart. + let mut request_receiver = bridge::get_request_receiver(); + // Repeat `crate::spawn` anywhere in your code + // if more concurrent tasks are needed. + crate::spawn(sample_functions::stream_mandelbrot()); + while let Some(request_unique) = request_receiver.recv().await { + crate::spawn(async { + let response_unique = handle_request(request_unique).await; + respond_to_dart(response_unique); + }); + } +} diff --git a/example/native/hub/src/sample_functions.rs b/example/native/hub/src/sample_functions.rs new file mode 100644 index 00000000..7dd27bed --- /dev/null +++ b/example/native/hub/src/sample_functions.rs @@ -0,0 +1,99 @@ +//! This module is only for demonstration purposes. +//! You might want to remove this module in production. + +use crate::bridge::api::{RustOperation, RustRequest, RustResponse, RustSignal}; +use crate::bridge::send_rust_signal; +use prost::Message; + +pub async fn handle_sample_resource(rust_request: RustRequest) -> RustResponse { + match rust_request.operation { + RustOperation::Create => RustResponse::default(), + RustOperation::Read => RustResponse::default(), + RustOperation::Update => RustResponse::default(), + RustOperation::Delete => RustResponse::default(), + } +} + +pub async fn handle_deeper_resource(rust_request: RustRequest) -> RustResponse { + match rust_request.operation { + RustOperation::Create => RustResponse::default(), + RustOperation::Read => RustResponse::default(), + RustOperation::Update => RustResponse::default(), + RustOperation::Delete => RustResponse::default(), + } +} + +pub async fn handle_counter_number(rust_request: RustRequest) -> RustResponse { + use crate::messages::counter_number::{ReadRequest, ReadResponse}; + // We import message structs in this handler function + // because schema will differ by Rust resource. + + match rust_request.operation { + RustOperation::Create => RustResponse::default(), + RustOperation::Read => { + // Decode raw bytes into a Rust message object. + let message_bytes = rust_request.message.unwrap(); + let request_message = ReadRequest::decode(message_bytes.as_slice()).unwrap(); + + // Perform a simple calculation. + let after_value: i32 = sample_crate::add_seven(request_message.before_number); + + // Return the response that will be sent to Dart. + let response_message = ReadResponse { + after_number: after_value, + dummy_one: request_message.dummy_one, + dummy_two: request_message.dummy_two, + dummy_three: request_message.dummy_three, + }; + RustResponse { + successful: true, + message: Some(response_message.encode_to_vec()), + blob: None, + } + } + RustOperation::Update => RustResponse::default(), + RustOperation::Delete => RustResponse::default(), + } +} + +pub async fn stream_mandelbrot() { + use crate::messages::mandelbrot::{StateSignal, ID}; + + let mut scale: f64 = 1.0; + + loop { + crate::sleep(std::time::Duration::from_millis(15)).await; + + scale *= 0.98; + if scale < 1e-7 { + scale = 1.0 + }; + + let calculated = sample_crate::mandelbrot( + sample_crate::Size { + width: 128, + height: 128, + }, + sample_crate::Point { + x: 0.360, + y: -0.641, + }, + scale, + 4, + ); + + if let Ok(mandelbrot) = calculated { + // Stream the signal to Dart. + let signal_message = StateSignal { + id: 0, + current_scale: scale, + }; + let rust_signal = RustSignal { + resource: ID, + message: Some(signal_message.encode_to_vec()), + blob: Some(mandelbrot), + }; + send_rust_signal(rust_signal); + } + } +} diff --git a/example/native/hub/src/web_alias.rs b/example/native/hub/src/web_alias.rs new file mode 100644 index 00000000..93f1a6f7 --- /dev/null +++ b/example/native/hub/src/web_alias.rs @@ -0,0 +1,70 @@ +//! The web has many restrictions due to its sandboxed environment +//! which prevents the use of +//! threads, atomics, time, file IO, network IO, +//! and many other native functionalities. +//! Consequently, certain features are missing from various crates +//! including Rust's `std` due to these limitations. +//! +//! To address this issue, this module offers various imports +//! with the **same names** as the original native ones, +//! providing workarounds for these constraints. +//! +//! You might encounter situations +//! where you cannot use native Rust code directly on the web. +//! Add more custom web aliases here if needed. +//! There are many crates at `crates.io` +//! that mimic native functionalities on the web +//! by interacting with JavaScript, +//! so use them if necessary. +//! +//! If your app is not targeting web, you can simply remove this module. + +#![allow(dead_code, unused_imports, unused_macros)] + +// On native platforms,`tokio`'s async runtime +// allows millions of concurrent tasks to run the same time +// utilizing only the number of threads +// equivalent to the number of cores on the computer. +// This is much more efficient and scalable than switching threads. +// +// On the web, async tasks are executed in the JavaScript event loop. +// Crate `wasm_bindgen_futures` has the ability +// to convert Rust `Future`s into JavaScript `Promise`s. + +#[cfg(not(target_family = "wasm"))] +pub(crate) fn spawn(future: T) +where + T: std::future::Future + Send + 'static, +{ + tokio::task::spawn(future); +} +#[cfg(target_family = "wasm")] +pub(crate) fn spawn(future: T) +where + T: std::future::Future + 'static, +{ + wasm_bindgen_futures::spawn_local(future); +} + +// On the web, `tokio` cannot access the system time. +// Crate `gloo_timers` handles sleeping and intervals on the web. + +pub async fn sleep(duration: std::time::Duration) { + #[cfg(not(target_family = "wasm"))] + tokio::time::sleep(duration).await; + #[cfg(target_family = "wasm")] + gloo_timers::future::sleep(duration).await; +} + +// On the web, the `println!` macro does not print to the browser console. +// Crate `web_sys` does something exactly like `console.log()` in JavaScript. + +#[macro_export] +#[cfg(target_family = "wasm")] +macro_rules! print { + ( $( $t:tt )* ) => { + web_sys::console::log_1(&format!( $( $t )* ).into()); + } +} +#[cfg(not(target_family = "wasm"))] +pub(crate) use println as print; diff --git a/example/native/hub/src/with_request.rs b/example/native/hub/src/with_request.rs new file mode 100644 index 00000000..99eef42d --- /dev/null +++ b/example/native/hub/src/with_request.rs @@ -0,0 +1,32 @@ +//! This module runs the corresponding function +//! when a `RustRequest` was received from Dart +//! and returns `RustResponse`. + +use crate::bridge::api::{RustRequestUnique, RustResponse, RustResponseUnique}; +use crate::messages; +use crate::sample_functions; + +pub async fn handle_request(request_unique: RustRequestUnique) -> RustResponseUnique { + // Get the request data. + let rust_request = request_unique.request; + let interaction_id = request_unique.id; + + // Run the function that corresponds to the address. + let rust_resource = rust_request.resource; + let rust_response = match rust_resource { + messages::counter_number::ID => sample_functions::handle_counter_number(rust_request).await, + messages::sample_folder::sample_resource::ID => { + sample_functions::handle_sample_resource(rust_request).await + } + messages::sample_folder::deeper_folder::deeper_resource::ID => { + sample_functions::handle_deeper_resource(rust_request).await + } + _ => RustResponse::default(), + }; + + // Return the response. + RustResponseUnique { + id: interaction_id, + response: rust_response, + } +} diff --git a/example/native/sample_crate/Cargo.toml b/example/native/sample_crate/Cargo.toml new file mode 100644 index 00000000..146e0228 --- /dev/null +++ b/example/native/sample_crate/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sample_crate" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.69" +num = "0.4" +image = "0.24.3" +crossbeam = "0.8" diff --git a/example/native/sample_crate/src/lib.rs b/example/native/sample_crate/src/lib.rs new file mode 100644 index 00000000..87d585e9 --- /dev/null +++ b/example/native/sample_crate/src/lib.rs @@ -0,0 +1,207 @@ +//! This crate is only for demonstration purposes. +//! You might want to remove this crate in production. +//! Mandelbrot is copied and modified from +//! https://github.com/ProgrammingRust/mandelbrot/blob/task-queue/src/main.rs and +//! https://github.com/Ducolnd/rust-mandelbrot/blob/master/src/main.rs + +use anyhow::*; +use image::codecs::png::PngEncoder; +use image::*; +use num::Complex; +use std::sync::Mutex; + +pub fn add_seven(before: i32) -> i32 { + before + 7 +} + +#[derive(Debug, Clone)] +pub struct Size { + pub width: i32, + pub height: i32, +} + +#[derive(Debug, Clone)] +pub struct Point { + pub x: f64, + pub y: f64, +} + +/// Try to determine if `c` is in the Mandelbrot set, using at most `limit` +/// iterations to decide. +/// +/// If `c` is not a member, return `Some(i)`, where `i` is the number of +/// iterations it took for `c` to leave the circle of radius two centered on the +/// origin. If `c` seems to be a member (more precisely, if we reached the +/// iteration limit without being able to prove that `c` is not a member), +/// return `None`. +fn escape_time(c: Complex, limit: usize) -> Option { + let mut z = Complex { re: 0.0, im: 0.0 }; + for i in 0..limit { + if z.norm_sqr() > 4.0 { + return Some(i); + } + z = z * z + c; + } + + None +} + +/// Given the row and column of a pixel in the output image, return the +/// corresponding point on the complex plane. +/// +/// `bounds` is a pair giving the width and height of the image in pixels. +/// `pixel` is a (column, row) pair indicating a particular pixel in that image. +/// The `upper_left` and `lower_right` parameters are points on the complex +/// plane designating the area our image covers. +fn pixel_to_point( + bounds: (usize, usize), + pixel: (usize, usize), + upper_left: Complex, + lower_right: Complex, +) -> Complex { + let (width, height) = ( + lower_right.re - upper_left.re, + upper_left.im - lower_right.im, + ); + Complex { + re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64, + im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64, + // Why subtraction here? pixel.1 increases as we go down, + // but the imaginary component increases as we go up. + } +} + +#[test] +fn test_pixel_to_point() { + assert_eq!( + pixel_to_point( + (100, 200), + (25, 175), + Complex { re: -1.0, im: 1.0 }, + Complex { re: 1.0, im: -1.0 }, + ), + Complex { + re: -0.5, + im: -0.75, + } + ); +} + +/// Render a rectangle of the Mandelbrot set into a buffer of pixels. +/// +/// The `bounds` argument gives the width and height of the buffer `pixels`, +/// which holds one grayscale pixel per byte. The `upper_left` and `lower_right` +/// arguments specify points on the complex plane corresponding to the upper- +/// left and lower-right corners of the pixel buffer. +fn render( + pixels: &mut [u8], + bounds: (usize, usize), + upper_left: Complex, + lower_right: Complex, +) { + assert_eq!(pixels.len(), bounds.0 * bounds.1); + + for row in 0..bounds.1 { + for column in 0..bounds.0 { + let point = pixel_to_point(bounds, (column, row), upper_left, lower_right); + pixels[row * bounds.0 + column] = match escape_time(point, 255) { + None => 0, + Some(count) => 255 - count as u8, + }; + } + } +} + +fn colorize(grey_pixels: &[u8]) -> Vec { + let mut ans = vec![0u8; grey_pixels.len() * 3]; + for i in 0..grey_pixels.len() { + let (r, g, b) = colorize_pixel(grey_pixels[i]); + ans[i * 3] = r; + ans[i * 3 + 1] = g; + ans[i * 3 + 2] = b; + } + ans +} + +const A: f64 = 1.0 * (1.0 / std::f64::consts::LOG2_10); +const B: f64 = (1.0 / (3.0 * std::f64::consts::SQRT_2)) * (1.0 / std::f64::consts::LOG2_10); + +pub fn colorize_pixel(it: u8) -> (u8, u8, u8) { + if it == 0 { + return (0, 0, 0); + } + let it = it as f64; + + let c: f64 = (1.0_f64 / ((7.0 * 3.0_f64).powf(1.0 / 8.0))) * (1.0 / std::f64::consts::LOG2_10); + + let r = 255.0 * ((1.0 - (A * it).cos()) / 2.0); + let g = 255.0 * ((1.0 - (B * it).cos()) / 2.0); + let b = 255.0 * ((1.0 - (c * it).cos()) / 2.0); + + // print!(" {:?} ", [r, g, b]); + + (r as u8, b as u8, g as u8) +} + +/// Write the buffer `pixels`, whose dimensions are given by `bounds`, to the +/// file named `filename`. +fn write_image(pixels: &[u8], bounds: (usize, usize)) -> Result> { + let mut buf = Vec::new(); + + let encoder = PngEncoder::new(&mut buf); + #[allow(deprecated)] + encoder.encode(pixels, bounds.0 as u32, bounds.1 as u32, ColorType::Rgb8)?; + + Ok(buf) +} + +pub fn mandelbrot( + image_size: Size, + zoom_point: Point, + scale: f64, + num_threads: i32, +) -> Result> { + let bounds = (image_size.width as usize, image_size.height as usize); + let upper_left = Complex::new(zoom_point.x - scale, zoom_point.y - scale); + let lower_right = Complex::new(zoom_point.x + scale, zoom_point.y + scale); + + let mut pixels = vec![0; bounds.0 * bounds.1]; + + let band_rows = bounds.1 / (num_threads as usize) + 1; + + { + let bands = Mutex::new(pixels.chunks_mut(band_rows * bounds.0).enumerate()); + let worker = || loop { + match { + let mut guard = bands.lock().unwrap(); + guard.next() + } { + None => { + return; + } + Some((i, band)) => { + let top = band_rows * i; + let height = band.len() / bounds.0; + let band_bounds = (bounds.0, height); + let band_upper_left = pixel_to_point(bounds, (0, top), upper_left, lower_right); + let band_lower_right = + pixel_to_point(bounds, (bounds.0, top + height), upper_left, lower_right); + render(band, band_bounds, band_upper_left, band_lower_right); + } + } + }; + + #[cfg(not(target_family = "wasm"))] + crossbeam::scope(|scope| { + for _ in 0..num_threads { + scope.spawn(|_| worker()); + } + }) + .unwrap(); + + #[cfg(target_family = "wasm")] + worker(); + } + + write_image(&colorize(&pixels), bounds) +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 00000000..ff2c07ae --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,98 @@ +name: example_app +description: Demonstrates how to use the rust_in_flutter plugin. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 0.1.0 + +environment: + sdk: ">=3.0.5 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + rust_in_flutter: + # When depending on this package from a real application you should use: + # rust_in_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + + protobuf: ^3.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..be820e83 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..096edf8f --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/example/windows/.gitignore b/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt new file mode 100644 index 00000000..9d06ebbc --- /dev/null +++ b/example/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..930d2071 --- /dev/null +++ b/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..a6a1dec4 --- /dev/null +++ b/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + rust_in_flutter +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc new file mode 100644 index 00000000..017ecd15 --- /dev/null +++ b/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.cunarist" "\0" + VALUE "FileDescription", "example_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.cunarist. All rights reserved." "\0" + VALUE "OriginalFilename", "example_app.exe" "\0" + VALUE "ProductName", "example_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp new file mode 100644 index 00000000..7d43e91f --- /dev/null +++ b/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/example/windows/runner/resource.h b/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/example/windows/runner/resources/app_icon.ico differ diff --git a/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp new file mode 100644 index 00000000..b2b08734 --- /dev/null +++ b/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/example/windows/runner/utils.h b/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/ios/Classes/rust_in_flutter.c b/ios/Classes/rust_in_flutter.c new file mode 100644 index 00000000..d850f883 --- /dev/null +++ b/ios/Classes/rust_in_flutter.c @@ -0,0 +1,3 @@ +// Relative import to be able to reuse the C sources. +// See the comment in ../{projectName}}.podspec for more information. +#include "../../src/rust_in_flutter.c" diff --git a/ios/rust_in_flutter.podspec b/ios/rust_in_flutter.podspec new file mode 100644 index 00000000..56143832 --- /dev/null +++ b/ios/rust_in_flutter.podspec @@ -0,0 +1,55 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_in_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_in_flutter' + s.version = '0.1.0' + s.summary = 'Summary' + s.description = 'Description' + s.homepage = 'http://cunarist.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@cunarist.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + + s.platform = :ios, '11.0' + s.swift_version = '5.0' + + script = <<-SCRIPT + echo "Generate protobuf message" + cd $PROJECT_DIR/../../ + dart run rust_in_flutter message + echo "Build Rust library" + sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" "$PROJECT_DIR/../../native/hub" hub + SCRIPT + + # Include Rust crates in the build process. + s.script_phase = { + :name => 'Generate protobuf message and build Rust library', + :script => script, + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + :output_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony_out', '${BUILT_PRODUCTS_DIR}/output.txt'], + } + s.pod_target_xcconfig = { + # From default Flutter template. + 'DEFINES_MODULE' => 'YES', + # Flutter framework does not contain a i386 slice. From default Flutter template. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + # Rust can't produce armv7 and it's being removed from Flutter as well. + 'EXCLUDED_ARCHS' => 'armv7', + # We use `-force_load` instead of `-l` since Xcode strips out unused symbols from static libraries. + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libhub.a', + 'DEAD_CODE_STRIPPING' => 'YES', + 'STRIP_INSTALLED_PRODUCT[config=*][sdk=*][arch=*]' => "YES", + 'STRIP_STYLE[config=*][sdk=*][arch=*]' => "non-global", + 'DEPLOYMENT_POSTPROCESSING[config=*][sdk=*][arch=*]' => "YES", + } +end diff --git a/lib/rust_in_flutter.dart b/lib/rust_in_flutter.dart new file mode 100644 index 00000000..b6e3f56e --- /dev/null +++ b/lib/rust_in_flutter.dart @@ -0,0 +1,83 @@ +/// This module supports communication with Rust. +/// More specifically, sending requests to Rust and +/// receiving stream signals from Rust are possible. + +import 'dart:math'; +import 'dart:async'; +import 'src/exports.dart'; + +export 'src/exports.dart' show RustOperation; +export 'src/exports.dart' show RustRequest; +export 'src/exports.dart' show RustResponse; +export 'src/exports.dart' show RustSignal; + +/// Listens to a stream from Rust and broadcasts the data in Dart. +/// You can see the usage example at +/// https://pub.dev/packages/rust_in_flutter/example. +final rustBroadcaster = StreamController.broadcast(); +final _responseBroadcaster = StreamController.broadcast(); +final _requestIdGenerator = _IdGenerator(); + +/// Contains basic functionalities of this framework. +class RustInFlutter { + /// Makes sure that the Rust side is ready. + /// Don't forget to call this function in the `main` function of Dart. + static Future ensureInitialized() async { + await api.prepareChannels(); + final rustSignalStream = api.prepareRustSignalStream(); + rustSignalStream.listen((rustSignal) { + rustBroadcaster.add(rustSignal); + }); + final rustResponseStream = api.prepareRustResponseStream(); + rustResponseStream.listen((responseUnique) { + _responseBroadcaster.add(responseUnique); + }); + while (!(await api.checkRustStreams())) {} + api.startRustLogic(); + } +} + +/// Sends bytes data to Rust wrapped in `RustRequest` object +/// with operation and address fields. +/// This system follows the definition of RESTful API +/// and is very similar to HTTP request. +/// Returns `RustResponse` object that is somehow calculated +/// from the Rust side. +/// If the Rust doesn't respond for 60 seconds, +/// this function will return a failed `RustResponse` object. +/// You can see the usage example at +/// https://pub.dev/packages/rust_in_flutter/example. +Future requestToRust(RustRequest rustRequest) async { + final id = _requestIdGenerator.generateId(); + final requestUnique = RustRequestUnique(id: id, request: rustRequest); + api.requestToRust(requestUnique: requestUnique); + final future = _responseBroadcaster.stream.firstWhere((responseUnique) { + return responseUnique.id == id; + }); + final responseUnique = await future.timeout( + const Duration(seconds: 60), + onTimeout: () { + return RustResponseUnique( + id: id, + response: RustResponse( + successful: false, + message: null, + blob: null, + ), + ); + }, + ); + final rustResponse = responseUnique.response; + return rustResponse; +} + +class _IdGenerator { + int _counter = -pow(2, 31).toInt(); + final _maxLimit = pow(2, 31).toInt() - 1; + int generateId() { + final id = _counter; + final increased = _counter + 1; + _counter = increased <= _maxLimit ? increased : -pow(2, 31).toInt(); + return id; + } +} diff --git a/lib/src/bridge_definitions.dart b/lib/src/bridge_definitions.dart new file mode 100644 index 00000000..cc965dd9 --- /dev/null +++ b/lib/src/bridge_definitions.dart @@ -0,0 +1,116 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import 'bridge_generated.io.dart' + if (dart.library.html) 'bridge_generated.web.dart'; +import 'dart:convert'; +import 'dart:async'; + +import 'bridge_engine/exports.dart'; + +abstract class Bridge { + /// Returns a stream object in Dart that listens to Rust. + Stream prepareRustSignalStream({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPrepareRustSignalStreamConstMeta; + + /// Returns a stream object in Dart that returns responses from Rust. + Stream prepareRustResponseStream({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPrepareRustResponseStreamConstMeta; + + /// Prepare channels that are used in the Rust world. + Future prepareChannels({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPrepareChannelsConstMeta; + + /// Check if the streams are ready in Rust. + /// This should be done before starting the Rust logic. + Future checkRustStreams({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kCheckRustStreamsConstMeta; + + /// Start the main function of Rust. + Future startRustLogic({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kStartRustLogicConstMeta; + + /// Send a request to Rust and receive a response in Dart. + Future requestToRust( + {required RustRequestUnique requestUnique, dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kRequestToRustConstMeta; +} + +/// Available operations that a `RustRequest` object can hold. +/// There are 4 options, `Create`,`Read`,`Update`, and `Delete`. +enum RustOperation { + Create, + Read, + Update, + Delete, +} + +/// Request object that is sent from Dart to Rust. +class RustRequest { + final int resource; + final RustOperation operation; + final Uint8List? message; + final Uint8List? blob; + + const RustRequest({ + required this.resource, + required this.operation, + this.message, + this.blob, + }); +} + +/// Wrapper for `RustRequest` with a unique ID. +class RustRequestUnique { + final int id; + final RustRequest request; + + const RustRequestUnique({ + required this.id, + required this.request, + }); +} + +/// Response object that is sent from Rust to Dart. +class RustResponse { + final bool successful; + final Uint8List? message; + final Uint8List? blob; + + const RustResponse({ + required this.successful, + this.message, + this.blob, + }); +} + +/// Wrapper for `RustResponse` with a unique ID. +class RustResponseUnique { + final int id; + final RustResponse response; + + const RustResponseUnique({ + required this.id, + required this.response, + }); +} + +/// Holds the data that Rust streams to Dart. +class RustSignal { + final int resource; + final Uint8List? message; + final Uint8List? blob; + + const RustSignal({ + required this.resource, + this.message, + this.blob, + }); +} diff --git a/lib/src/bridge_engine/basic.dart b/lib/src/bridge_engine/basic.dart new file mode 100644 index 00000000..f84084d6 --- /dev/null +++ b/lib/src/bridge_engine/basic.dart @@ -0,0 +1,190 @@ +import 'dart:async'; + +import 'platform_independent.dart'; +import 'utils.dart'; +import 'ffi.dart'; +export 'ffi.dart'; +import 'isolate.dart'; +export 'isolate.dart'; + +final _instances = {}; +final _streamSinkNameIndex = {}; + +class _DropIdPortGenerator { + static final instance = _DropIdPortGenerator._(); + _DropIdPortGenerator._(); + + int nextPort = 0; + String create() => '__frb_dart_opaque_drop_${nextPort++}'; +} + +/// Base class for generated bindings of Flutter Rust Bridge. +/// Normally, users do not extend this class manually. Instead, +/// users should directly use the generated class. +abstract class FlutterRustBridgeBase { + FlutterRustBridgeBase(this.inner) { + _sanityCheckSingleton(); + _setUpRustToDartComm(); + } + + final T inner; + + late final _dropPort = _initDropPort(); + NativePortType get dropPort => _dropPort.sendPort.nativePort; + + ReceivePort _initDropPort() { + var port = broadcastPort(_DropIdPortGenerator.instance.create()); + port.listen((message) { + inner.drop_dart_object(message); + }); + return port; + } + + void dispose() { + _dropPort.close(); + } + + void _sanityCheckSingleton() { + if (_instances.contains(runtimeType)) { + throw Exception( + 'Subclasses of `FlutterRustBridgeBase` should be singletons - ' + 'there should not be two instances (runtimeType=$runtimeType)', + ); + } + _instances.add(runtimeType); + } + + void _setUpRustToDartComm() { + inner.storeDartPostCObject(); + } + + /// Execute a normal ffi call. Usually called by generated code instead of manually called. + + Future executeNormal(FlutterRustBridgeTask task) { + final completer = Completer(); + final sendPort = singleCompletePort(completer); + task.callFfi(sendPort.nativePort); + return completer.future.then((dynamic raw) => + _transformRust2DartMessage(raw, task.parseSuccessData)); + } + + /// Similar to [executeNormal], except that this will return synchronously + + S executeSync(FlutterRustBridgeSyncTask task) { + final WireSyncReturn syncReturn; + try { + syncReturn = task.callFfi(); + } catch (err, st) { + throw FfiException('EXECUTE_SYNC_ABORT', '$err', st); + } + try { + final syncReturnAsDartObject = wireSyncReturnIntoDart(syncReturn); + assert(syncReturnAsDartObject.length == 2); + final rawReturn = syncReturnAsDartObject[0]; + final isSuccess = syncReturnAsDartObject[1]; + if (isSuccess) { + return task.parseSuccessData(rawReturn); + } else { + throw FfiException('EXECUTE_SYNC', rawReturn as String, null); + } + } catch (err, st) { + if (err is FfiException) rethrow; + throw FfiException('EXECUTE_SYNC_ABORT', '$err', st); + } finally { + inner.free_WireSyncReturn(syncReturn); + } + } + + /// Similar to [executeNormal], except that this will return a [Stream] instead of a [Future]. + + Stream executeStream(FlutterRustBridgeTask task) async* { + final func = task.constMeta.debugName; + final nextIndex = _streamSinkNameIndex.update(func, (value) => value + 1, + ifAbsent: () => 0); + final name = '__frb_streamsink_${func}_$nextIndex'; + final receivePort = broadcastPort(name); + task.callFfi(receivePort.sendPort.nativePort); + + await for (final raw in receivePort) { + try { + yield _transformRust2DartMessage(raw, task.parseSuccessData); + } on _CloseStreamException { + receivePort.close(); + break; + } + } + } + + S _transformRust2DartMessage( + List raw, S Function(dynamic) parseSuccessData) { + final action = raw[0]; + switch (action) { + case _RUST2DART_ACTION_SUCCESS: + assert(raw.length == 2); + return parseSuccessData(raw[1]); + case _RUST2DART_ACTION_ERROR: + assert(raw.length == 4); + throw FfiException(raw[1], raw[2], raw[3]); + case _RUST2DART_ACTION_CLOSE_STREAM: + assert(raw.length == 1); + throw _CloseStreamException(); + default: + throw Exception('Unsupported message, action=$action raw=$raw'); + } + } + + // ignore: constant_identifier_names + static const _RUST2DART_ACTION_SUCCESS = 0; + + // ignore: constant_identifier_names + static const _RUST2DART_ACTION_ERROR = 1; + + // ignore: constant_identifier_names + static const _RUST2DART_ACTION_CLOSE_STREAM = 2; +} + +/// A task to call FFI function. + +class FlutterRustBridgeTask extends FlutterRustBridgeBaseTask { + /// The underlying function to call FFI function, usually the generated wire function + final void Function(NativePortType port) callFfi; + + /// Parse the returned data from the underlying function + final S Function(dynamic) parseSuccessData; + + const FlutterRustBridgeTask({ + required this.callFfi, + required this.parseSuccessData, + required FlutterRustBridgeTaskConstMeta constMeta, + required List argValues, + required dynamic hint, + }) : super( + constMeta: constMeta, + argValues: argValues, + hint: hint, + ); +} + +/// A task to call FFI function, but it is synchronous. + +class FlutterRustBridgeSyncTask extends FlutterRustBridgeBaseTask { + /// The underlying function to call FFI function, usually the generated wire function + final WireSyncReturn Function() callFfi; + + /// Parse the returned data from the underlying function + final S Function(dynamic) parseSuccessData; + + const FlutterRustBridgeSyncTask({ + required this.callFfi, + required this.parseSuccessData, + required FlutterRustBridgeTaskConstMeta constMeta, + required List argValues, + required dynamic hint, + }) : super( + constMeta: constMeta, + argValues: argValues, + hint: hint, + ); +} + +class _CloseStreamException {} diff --git a/lib/src/bridge_engine/exports.dart b/lib/src/bridge_engine/exports.dart new file mode 100644 index 00000000..95e918cc --- /dev/null +++ b/lib/src/bridge_engine/exports.dart @@ -0,0 +1,5 @@ +export 'basic.dart'; +export 'helpers.dart'; +export 'platform_independent.dart'; +export 'typed_data.dart'; +export 'load.dart'; diff --git a/lib/src/bridge_engine/ffi.dart b/lib/src/bridge_engine/ffi.dart new file mode 100644 index 00000000..5e440916 --- /dev/null +++ b/lib/src/bridge_engine/ffi.dart @@ -0,0 +1,85 @@ +import 'ffi/io.dart' if (dart.library.html) 'ffi/web.dart'; + +export 'ffi/stub.dart' + if (dart.library.io) 'ffi/io.dart' + if (dart.library.html) 'ffi/web.dart'; + +typedef DropFnType = void Function(PlatformPointer); +typedef ShareFnType = PlatformPointer Function(PlatformPointer); + +/// Rust SyncReturn type is forced cast to u64. +const syncReturnPointerLength = 8; + +/// An opaque pointer to a native C or Rust type. +/// Recipients of this type should call [dispose] at least once during runtime. +/// If passed to a native function after being [dispose]d, an exception will be thrown. +abstract class FrbOpaque extends FrbOpaqueBase { + /// Pointer to this opaque Rust type. + PlatformPointer _ptr; + + /// A native finalizer rust opaque type. + /// Is static for each frb api class instance. + OpaqueTypeFinalizer get staticFinalizer; + + /// Displays the need to release ownership when sending to rust. + bool _move = false; + set move(bool move) => _move = move; + + /// Rust type specific drop function. + /// + /// This function should never be called manually. + DropFnType get dropFn; + + /// Rust type specific share function. + /// + /// This function should never be called manually. + ShareFnType get shareFn; + + /// This constructor should never be called manually. + + FrbOpaque.unsafe(int ptr, int size) : _ptr = FrbOpaqueBase.initPtr(ptr) { + if (ptr != 0) { + FrbOpaqueBase.finalizerAttach(this, _ptr, size, staticFinalizer); + } + } + + /// Call Rust destructors on the backing memory of this pointer. + /// + /// This function should be run at least once during the lifetime of the + /// program, and can be run many times. + /// + /// When passed into a Rust function, Rust enacts *shared ownership*, + /// if this pointer is shared with Rust when [dispose] is called, + /// ownership is fully transferred to Rust else this pointer is cleared. + void dispose() { + if (!isStale()) { + var ptr = _ptr; + _ptr = FrbOpaqueBase.nullPtr(); + + staticFinalizer.detach(this); + dropFn(ptr); + } + } + + /// Increments inner reference counter and returns pointer to the underlying + /// Rust object. + /// + /// Throws a [StateError] if called after [dispose]. + + PlatformPointer shareOrMove() { + if (!isStale()) { + var ptr = shareFn(_ptr); + if (_move) { + dispose(); + } + return ptr; + } else { + return FrbOpaqueBase.nullPtr(); + } + } + + /// Checks whether [dispose] has been called at any point during the lifetime + /// of this pointer. This does not guarantee that the backing memory has + /// actually been reclaimed. + bool isStale() => FrbOpaqueBase.isStalePtr(_ptr); +} diff --git a/lib/src/bridge_engine/ffi/dart_cobject.dart b/lib/src/bridge_engine/ffi/dart_cobject.dart new file mode 100644 index 00000000..d22c999b --- /dev/null +++ b/lib/src/bridge_engine/ffi/dart_cobject.dart @@ -0,0 +1,174 @@ +import 'dart:convert'; +import 'dart:ffi' as ffi; +import 'dart:ffi'; +export 'dart:ffi' show NativePort, DynamicLibrary; +import 'dart:typed_data'; +import 'dart_native_api.dart'; +export 'dart_native_api.dart' show Dart_CObject; + +extension DartCObjectExt on Dart_CObject { + dynamic intoDart() { + switch (type) { + case Dart_CObject_Type.Dart_CObject_kNull: + return null; + case Dart_CObject_Type.Dart_CObject_kBool: + return value.as_bool; + case Dart_CObject_Type.Dart_CObject_kInt32: + return value.as_int32; + case Dart_CObject_Type.Dart_CObject_kInt64: + return value.as_int64; + case Dart_CObject_Type.Dart_CObject_kDouble: + return value.as_double; + + case Dart_CObject_Type.Dart_CObject_kString: + // `DartCObject` strings being encoded with std::ffi::CString assert us nul-termination. + // See [allo-isolate's String::into_dart](https://github.com/sunshine-protocol/allo-isolate/blob/71b9760993d64ef46794176ca276d1cc637b2599/src/into_dart.rs#L106) + // and [std::ffi::CString](https://doc.rust-lang.org/nightly/std/ffi/struct.CString.html) + int len = 0; + while (value.as_string.elementAt(len).value != 0) { + len++; + } + return utf8.decode(value.as_string.cast().asTypedList(len)); + + case Dart_CObject_Type.Dart_CObject_kArray: + return List.generate(value.as_array.length, + (i) => value.as_array.values.elementAt(i).value.ref.intoDart()); + + case Dart_CObject_Type.Dart_CObject_kTypedData: + return _typedDataIntoDart( + value.as_typed_data.type, + value.as_typed_data.values, + value.as_typed_data.length, + ).clone(); + + case Dart_CObject_Type.Dart_CObject_kExternalTypedData: + final externalTypedData = _typedDataIntoDart( + value.as_external_typed_data.type, + value.as_external_typed_data.data, + value.as_external_typed_data.length, + ).view; + _externalTypedDataFinalizer.attach( + externalTypedData, + // Copy the cleanup info into a finalization token: + // `value`'s underlying memory will probably be freed + // before the zero-copy finalizer is called. + _ExternalTypedDataFinalizerArgs( + length: value.as_external_typed_data.length, + peer: value.as_external_typed_data.peer, + callback: value.as_external_typed_data.callback + .cast>(), + ), + ); + return externalTypedData; + + case Dart_CObject_Type.Dart_CObject_kSendPort: + case Dart_CObject_Type.Dart_CObject_kCapability: + case Dart_CObject_Type.Dart_CObject_kNativePointer: + case Dart_CObject_Type.Dart_CObject_kUnsupported: + case Dart_CObject_Type.Dart_CObject_kNumberOfTypes: + default: + throw Exception("Can't read invalid data type $type"); + } + } + + static _TypedData _typedDataIntoDart( + int ty, + ffi.Pointer typedValues, + int nValues, + ) { + switch (ty) { + case Dart_TypedData_Type.Dart_TypedData_kByteData: + return _TypedData( + ByteData.view( + typedValues.cast().asTypedList(nValues).buffer, + ), + (view) => ByteData.view( + Uint8List.fromList(view.buffer.asUint8List()).buffer, + ), + ); + case Dart_TypedData_Type.Dart_TypedData_kInt8: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Int8List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kUint8: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Uint8List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kInt16: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Int16List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kUint16: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Uint16List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kInt32: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Int32List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kUint32: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Uint32List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kInt64: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Int64List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kUint64: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Uint64List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kFloat32: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Float32List.fromList); + case Dart_TypedData_Type.Dart_TypedData_kFloat64: + final view = typedValues.cast().asTypedList(nValues); + return _TypedData(view, Float64List.fromList); + + case Dart_TypedData_Type.Dart_TypedData_kUint8Clamped: + case Dart_TypedData_Type.Dart_TypedData_kFloat32x4: + case Dart_TypedData_Type.Dart_TypedData_kInvalid: + default: + throw Exception("Can't read invalid typed data type $ty"); + } + } + + static final _externalTypedDataFinalizer = + Finalizer<_ExternalTypedDataFinalizerArgs>((externalTypedData) { + final handleFinalizer = + externalTypedData.callback.asFunction(); + handleFinalizer(externalTypedData.length, externalTypedData.peer); + + if (bool.fromEnvironment("ENABLE_FRB_FFI_TEST_TOOL")) { + for (var handler in testTool!.onExternalTypedDataFinalizer) { + handler(externalTypedData.length); + } + } + }); +} + +class _TypedData { + final T view; + final T Function(T) _cloneView; + _TypedData(this.view, this._cloneView); + + T clone() => _cloneView(view); +} + +class _ExternalTypedDataFinalizerArgs { + final int length; + final ffi.Pointer peer; + final ffi.Pointer> + callback; + + _ExternalTypedDataFinalizerArgs({ + required this.length, + required this.peer, + required this.callback, + }); +} + +typedef NativeExternalTypedDataFinalizer = ffi.Void Function( + ffi.IntPtr, ffi.Pointer); +typedef DartExternalTypedDataFinalizer = void Function( + int, ffi.Pointer); + +class TestTool { + final Set onExternalTypedDataFinalizer = {}; + TestTool._(); +} + +final testTool = + bool.fromEnvironment("ENABLE_FRB_FFI_TEST_TOOL") ? TestTool._() : null; diff --git a/lib/src/bridge_engine/ffi/dart_native_api.dart b/lib/src/bridge_engine/ffi/dart_native_api.dart new file mode 100644 index 00000000..c6e427e0 --- /dev/null +++ b/lib/src/bridge_engine/ffi/dart_native_api.dart @@ -0,0 +1,344 @@ +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +/// generated by flutter_dart +class DartCObject { + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + DartCObject(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + DartCObject.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + /// Posts a message on some port. The message will contain the Dart_CObject + /// object graph rooted in 'message'. + /// + /// While the message is being sent the state of the graph of Dart_CObject + /// structures rooted in 'message' should not be accessed, as the message + /// generation will make temporary modifications to the data. When the message + /// has been sent the graph will be fully restored. + /// + /// If true is returned, the message was enqueued, and finalizers for external + /// typed data will eventually run, even if the receiving isolate shuts down + /// before processing the message. If false is returned, the message was not + /// enqueued and ownership of external typed data in the message remains with the + /// caller. + /// + /// This function may be called on any thread when the VM is running (that is, + /// after Dart_Initialize has returned and before Dart_Cleanup has been called). + /// + /// \param port_id The destination port. + /// \param message The message to send. + /// + /// \return True if the message was posted. + bool Dart_PostCObject( + int port_id, + ffi.Pointer message, + ) { + return _Dart_PostCObject( + port_id, + message, + ); + } + + late final _Dart_PostCObjectPtr = _lookup< + ffi.NativeFunction< + ffi.Bool Function( + Dart_Port, ffi.Pointer)>>('Dart_PostCObject'); + late final _Dart_PostCObject = _Dart_PostCObjectPtr.asFunction< + bool Function(int, ffi.Pointer)>(); + + /// Posts a message on some port. The message will contain the integer 'message'. + /// + /// \param port_id The destination port. + /// \param message The message to send. + /// + /// \return True if the message was posted. + bool Dart_PostInteger( + int port_id, + int message, + ) { + return _Dart_PostInteger( + port_id, + message, + ); + } + + late final _Dart_PostIntegerPtr = + _lookup>( + 'Dart_PostInteger'); + late final _Dart_PostInteger = + _Dart_PostIntegerPtr.asFunction(); + + /// Creates a new native port. When messages are received on this + /// native port, then they will be dispatched to the provided native + /// message handler. + /// + /// \param name The name of this port in debugging messages. + /// \param handler The C handler to run when messages arrive on the port. + /// \param handle_concurrently Is it okay to process requests on this + /// native port concurrently? + /// + /// \return If successful, returns the port id for the native port. In + /// case of error, returns ILLEGAL_PORT. + int Dart_NewNativePort( + ffi.Pointer name, + Dart_NativeMessageHandler handler, + bool handle_concurrently, + ) { + return _Dart_NewNativePort( + name, + handler, + handle_concurrently, + ); + } + + late final _Dart_NewNativePortPtr = _lookup< + ffi.NativeFunction< + Dart_Port Function(ffi.Pointer, Dart_NativeMessageHandler, + ffi.Bool)>>('Dart_NewNativePort'); + late final _Dart_NewNativePort = _Dart_NewNativePortPtr.asFunction< + int Function(ffi.Pointer, Dart_NativeMessageHandler, bool)>(); + + /// Closes the native port with the given id. + /// + /// The port must have been allocated by a call to Dart_NewNativePort. + /// + /// \param native_port_id The id of the native port to close. + /// + /// \return Returns true if the port was closed successfully. + bool Dart_CloseNativePort( + int native_port_id, + ) { + return _Dart_CloseNativePort( + native_port_id, + ); + } + + late final _Dart_CloseNativePortPtr = + _lookup>( + 'Dart_CloseNativePort'); + late final _Dart_CloseNativePort = + _Dart_CloseNativePortPtr.asFunction(); + + /// Forces all loaded classes and functions to be compiled eagerly in + /// the current isolate. + Object Dart_CompileAll() { + return _Dart_CompileAll(); + } + + late final _Dart_CompileAllPtr = + _lookup>('Dart_CompileAll'); + late final _Dart_CompileAll = + _Dart_CompileAllPtr.asFunction(); + + /// Finalizes all classes. + Object Dart_FinalizeAllClasses() { + return _Dart_FinalizeAllClasses(); + } + + late final _Dart_FinalizeAllClassesPtr = + _lookup>( + 'Dart_FinalizeAllClasses'); + late final _Dart_FinalizeAllClasses = + _Dart_FinalizeAllClassesPtr.asFunction(); + + ffi.Pointer Dart_ExecuteInternalCommand( + ffi.Pointer command, + ffi.Pointer arg, + ) { + return _Dart_ExecuteInternalCommand( + command, + arg, + ); + } + + late final _Dart_ExecuteInternalCommandPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Pointer, + ffi.Pointer)>>('Dart_ExecuteInternalCommand'); + late final _Dart_ExecuteInternalCommand = + _Dart_ExecuteInternalCommandPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer)>(); +} + +/// A Dart_CObject is used for representing Dart objects as native C +/// data outside the Dart heap. These objects are totally detached from +/// the Dart heap. Only a subset of the Dart objects have a +/// representation as a Dart_CObject. +/// +/// The string encoding in the 'value.as_string' is UTF-8. +/// +/// All the different types from dart:typed_data are exposed as type +/// kTypedData. The specific type from dart:typed_data is in the type +/// field of the as_typed_data structure. The length in the +/// as_typed_data structure is always in bytes. +/// +/// The data for kTypedData is copied on message send and ownership remains with +/// the caller. The ownership of data for kExternalTyped is passed to the VM on +/// message send and returned when the VM invokes the +/// Dart_HandleFinalizer callback; a non-NULL callback must be provided. +/// +/// Note that Dart_CObject_kNativePointer is intended for internal use by +/// dart:io implementation and has no connection to dart:ffi Pointer class. +/// It represents a pointer to a native resource of a known type. +/// The receiving side will only see this pointer as an integer and will not +/// see the specified finalizer. +/// The specified finalizer will only be invoked if the message is not delivered. +abstract class Dart_CObject_Type { + static const int Dart_CObject_kNull = 0; + static const int Dart_CObject_kBool = 1; + static const int Dart_CObject_kInt32 = 2; + static const int Dart_CObject_kInt64 = 3; + static const int Dart_CObject_kDouble = 4; + static const int Dart_CObject_kString = 5; + static const int Dart_CObject_kArray = 6; + static const int Dart_CObject_kTypedData = 7; + static const int Dart_CObject_kExternalTypedData = 8; + static const int Dart_CObject_kSendPort = 9; + static const int Dart_CObject_kCapability = 10; + static const int Dart_CObject_kNativePointer = 11; + static const int Dart_CObject_kUnsupported = 12; + static const int Dart_CObject_kUnmodifiableExternalTypedData = 13; + static const int Dart_CObject_kNumberOfTypes = 14; +} + +final class _Dart_CObject extends ffi.Struct { + @ffi.Int32() + external int type; + + external UnnamedUnion1 value; +} + +final class UnnamedUnion1 extends ffi.Union { + @ffi.Bool() + external bool as_bool; + + @ffi.Int32() + external int as_int32; + + @ffi.Int64() + external int as_int64; + + @ffi.Double() + external double as_double; + + external ffi.Pointer as_string; + + external UnnamedStruct1 as_send_port; + + external UnnamedStruct2 as_capability; + + external UnnamedStruct3 as_array; + + external UnnamedStruct4 as_typed_data; + + external UnnamedStruct5 as_external_typed_data; + + external UnnamedStruct6 as_native_pointer; +} + +final class UnnamedStruct1 extends ffi.Struct { + @Dart_Port() + external int id; + + @Dart_Port() + external int origin_id; +} + +/// A port is used to send or receive inter-isolate messages +typedef Dart_Port = ffi.Int64; + +final class UnnamedStruct2 extends ffi.Struct { + @ffi.Int64() + external int id; +} + +final class UnnamedStruct3 extends ffi.Struct { + @ffi.IntPtr() + external int length; + + external ffi.Pointer> values; +} + +final class UnnamedStruct4 extends ffi.Struct { + @ffi.Int32() + external int type; + + @ffi.IntPtr() + external int length; + + external ffi.Pointer values; +} + +abstract class Dart_TypedData_Type { + static const int Dart_TypedData_kByteData = 0; + static const int Dart_TypedData_kInt8 = 1; + static const int Dart_TypedData_kUint8 = 2; + static const int Dart_TypedData_kUint8Clamped = 3; + static const int Dart_TypedData_kInt16 = 4; + static const int Dart_TypedData_kUint16 = 5; + static const int Dart_TypedData_kInt32 = 6; + static const int Dart_TypedData_kUint32 = 7; + static const int Dart_TypedData_kInt64 = 8; + static const int Dart_TypedData_kUint64 = 9; + static const int Dart_TypedData_kFloat32 = 10; + static const int Dart_TypedData_kFloat64 = 11; + static const int Dart_TypedData_kInt32x4 = 12; + static const int Dart_TypedData_kFloat32x4 = 13; + static const int Dart_TypedData_kFloat64x2 = 14; + static const int Dart_TypedData_kInvalid = 15; +} + +final class UnnamedStruct5 extends ffi.Struct { + @ffi.Int32() + external int type; + + @ffi.IntPtr() + external int length; + + external ffi.Pointer data; + + external ffi.Pointer peer; + + external Dart_HandleFinalizer callback; +} + +typedef Dart_HandleFinalizer = ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer isolate_callback_data, + ffi.Pointer peer)>>; + +final class UnnamedStruct6 extends ffi.Struct { + @ffi.IntPtr() + external int ptr; + + @ffi.IntPtr() + external int size; + + external Dart_HandleFinalizer callback; +} + +typedef Dart_CObject = _Dart_CObject; + +/// A native message handler. +/// +/// This handler is associated with a native port by calling +/// Dart_NewNativePort. +/// +/// The message received is decoded into the message structure. The +/// lifetime of the message data is controlled by the caller. All the +/// data references from the message are allocated by the caller and +/// will be reclaimed when returning to it. +typedef Dart_NativeMessageHandler = ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + Dart_Port dest_port_id, ffi.Pointer message)>>; diff --git a/lib/src/bridge_engine/ffi/io.dart b/lib/src/bridge_engine/ffi/io.dart new file mode 100644 index 00000000..338c8782 --- /dev/null +++ b/lib/src/bridge_engine/ffi/io.dart @@ -0,0 +1,53 @@ +import 'dart:ffi' as ffi; +import 'dart:ffi'; +export 'dart:ffi' show NativePort, DynamicLibrary; +import 'dart_cobject.dart'; + +import 'stub.dart' show FlutterRustBridgeWireBase; +export 'stub.dart' + show castInt, castNativeBigInt, FlutterRustBridgeWireBase, WasmModule; + +/// Abstraction over a Dart SendPort and a JS MessagePort. +typedef NativePortType = int; +typedef ExternalLibrary = ffi.DynamicLibrary; +typedef DartPostCObject = ffi.Pointer< + ffi.NativeFunction)>>; + +extension StoreDartPostCObjectExt on FlutterRustBridgeWireBase { + void storeDartPostCObject() { + store_dart_post_cobject(ffi.NativeApi.postCObject.cast()); + } +} + +class DartApiDl { + static int? _initCode; + final int Function(ffi.Pointer) _initFn; + DartApiDl(this._initFn); + + void initApi() { + _initCode ??= _initFn(ffi.NativeApi.initializeApiDLData); + if (_initCode != 0) { + throw Exception('Failed to initialize Dart API. Code: $_initCode'); + } + } +} + +typedef WireSyncReturn = ffi.Pointer; + +List wireSyncReturnIntoDart(WireSyncReturn syncReturn) => + syncReturn.ref.intoDart(); + +typedef PlatformPointer = ffi.Pointer; +typedef OpaqueTypeFinalizer = NativeFinalizer; + +/// An opaque pointer to a native C or Rust type. +/// Recipients of this type should call [dispose] at least once during runtime. +/// If passed to a native function after being [dispose]d, an exception will be thrown. +class FrbOpaqueBase implements Finalizable { + static PlatformPointer initPtr(int ptr) => ffi.Pointer.fromAddress(ptr); + static PlatformPointer nullPtr() => ffi.Pointer.fromAddress(0); + static bool isStalePtr(PlatformPointer ptr) => ptr.address == 0; + static void finalizerAttach(FrbOpaqueBase opaque, PlatformPointer ptr, + int size, OpaqueTypeFinalizer finalizer) => + finalizer.attach(opaque, ptr, detach: opaque, externalSize: size); +} diff --git a/lib/src/bridge_engine/ffi/stub.dart b/lib/src/bridge_engine/ffi/stub.dart new file mode 100644 index 00000000..a419e6d8 --- /dev/null +++ b/lib/src/bridge_engine/ffi/stub.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'io.dart' if (dart.library.html) 'web.dart' + show DartPostCObject, NativePortType, WireSyncReturn; +export 'io.dart' if (dart.library.html) 'web.dart' + show + ExternalLibrary, + WireSyncReturn, + FrbOpaqueBase, + DartApiDl, + NativePortType, + PlatformPointer, + OpaqueTypeFinalizer; +import 'package:rust_in_flutter/src/bridge_engine/isolate.dart' show SendPort; + +/// This class, together with its subclasses, are only for internal usage. +/// Usually it should not be used by normal users. +abstract class FlutterRustBridgeWireBase { + /// Not to be used by normal users, but has to be public for generated code + // ignore: non_constant_identifier_names + void store_dart_post_cobject(DartPostCObject ptr) { + throw UnimplementedError(); + } + + // ignore: non_constant_identifier_names + Object get_dart_object(int ptr) { + throw UnimplementedError(); + } + + // ignore: non_constant_identifier_names + void drop_dart_object(int ptr) { + throw UnimplementedError(); + } + + // ignore: non_constant_identifier_names + int new_dart_opaque(Object obj) { + throw UnimplementedError(); + } + + /// Not to be used by normal users, but has to be public for generated code + // ignore: non_constant_identifier_names + void free_WireSyncReturn(WireSyncReturn val) { + throw UnimplementedError(); + } +} + +extension NativeType on SendPort { + NativePortType get nativePort => throw UnimplementedError(); +} + +extension StoreDartPostCObjectExt on FlutterRustBridgeWireBase { + void storeDartPostCObject() => throw UnimplementedError(); +} + +/// Generates the dynamic Dart object from either an FFI struct or a JS value +List wireSyncReturnIntoDart(WireSyncReturn syncReturn) => + throw UnimplementedError(); + +/// Whether the web platform has been isolated by COOP and COEP headers, +/// and is capable of sharing buffers between workers. +/// +/// Note: not available on all browsers, in which case it will return null. +bool? get crossOriginIsolated => throw UnimplementedError(); + +int castInt(Object? value) => value as int; + +/// Only used on the Web. +Object castNativeBigInt(int value) => throw UnimplementedError(); + +abstract class FlutterRustBridgeWasmWireBase + extends FlutterRustBridgeWireBase { + Future get init => throw UnimplementedError(); + FlutterRustBridgeWasmWireBase([FutureOr? module]); +} + +class JS { + const JS([String? name]); +} + +class _Anonymous { + const _Anonymous(); +} + +const anonymous = _Anonymous(); + +dynamic eval(String script) => throw UnimplementedError(); + +/// A JS function that returns a Promise to a WASM module. +/// +/// ## Enabling cross-origin isolation +/// Rust WASM modules do not work without cross-origin isolation. +abstract class WasmModule { + Object call([String? moduleName]); + + /// Create a new WASM module initializer that is bound to the specified binary. + WasmModule bind(dynamic thisArg, String moduleName); + + static Future cast(FutureOr module) { + return Future.value(module).then((module) => module as T); + } + + /// Initialize a [WasmModule] with the specified kind of [Modules]. + static FutureOr initialize( + {required Modules kind, WasmModule Function()? module}) => + throw UnimplementedError(); +} + +/// Currently supported modes of module initialization. +/// +/// Advanced users may wish to inherit this class and override [initializeModule] +/// to provide their own initialization process. +abstract class Modules { + const Modules(); + + /// Initialize a `wasm_bindgen` module built with the `-t no-modules` flag. + /// + /// The expected output is a file named `$root.js` and the accompanying + /// WASM binary named `${root}_bg.wasm`. + const factory Modules.noModules({required String root}) = + _WasmBindgenNoModules; + + /// How a WASM module is brought into Dart's scope and initialized. + /// + /// Override this method to define custom initialization processes. + FutureOr initializeModule(WasmModule Function()? module); +} + +class _WasmBindgenNoModules extends Modules { + final String root; + const _WasmBindgenNoModules({required this.root}); + + @override + FutureOr initializeModule(WasmModule Function()? module) => + throw UnimplementedError(); +} diff --git a/lib/src/bridge_engine/ffi/web.dart b/lib/src/bridge_engine/ffi/web.dart new file mode 100644 index 00000000..ebdb7905 --- /dev/null +++ b/lib/src/bridge_engine/ffi/web.dart @@ -0,0 +1,147 @@ +// ignore_for_file: avoid_web_libraries_in_flutter + +import 'dart:async'; +import 'dart:html'; + +import 'package:rust_in_flutter/src/bridge_engine/exports.dart'; +export 'package:js/js.dart'; +import 'package:js/js_util.dart'; +export 'package:js/js_util.dart' show promiseToFuture, getProperty; + +@JS() +abstract class WasmModule { + Object call([String? moduleName]); + + /// Create a new WASM module initializer that is bound to the specified binary. + WasmModule bind(dynamic thisArg, String moduleName); + + static Future cast(FutureOr module) { + return Future.value(module).then((module) => module as T); + } + + static FutureOr initialize( + {required Modules kind, WasmModule Function()? module}) => + kind.initializeModule(module); +} + +abstract class Modules { + const Modules(); + + const factory Modules.noModules({required String root}) = + _WasmBindgenNoModules; + + FutureOr initializeModule(WasmModule Function()? module); + + void _ensureCrossOriginIsolated() { + switch (crossOriginIsolated) { + case false: + throw const MissingHeaderException(); + case true: + case null: + // On some browsers, this global variable is not available, + // which means that Dart cannot determine + // whether the browser supports buffer sharing. + return; + } + } +} + +class _WasmBindgenNoModules extends Modules { + final String root; + const _WasmBindgenNoModules({required this.root}); + + @override + FutureOr initializeModule(WasmModule Function()? module) { + _ensureCrossOriginIsolated(); + final script = ScriptElement()..src = '$root.js'; + document.head!.append(script); + return script.onLoad.first.then((_) { + eval('window.wasm_bindgen = wasm_bindgen'); + final module_ = module?.call() ?? _noModules!; + return module_.bind(null, '${root}_bg.wasm'); + }); + } +} + +typedef ExternalLibrary = FutureOr; +typedef DartPostCObject = void; + +@JS() +external bool? get crossOriginIsolated; + +@JS('console.warn') +external void warn([a, b, c, d, e, f, g, h, i]); + +@JS('Number') +external int castInt(Object? value); + +@JS('BigInt') +external Object castNativeBigInt(Object? value); + +@JS('Function') +class _Function { + external dynamic call(); + external factory _Function(String script); +} + +@JS('wasm_bindgen') +external WasmModule? get _noModules; + +dynamic eval(String script) => _Function(script)(); + +abstract class DartApiDl {} + +@JS("wasm_bindgen.get_dart_object") +// ignore: non_constant_identifier_names +external Object getDartObject(int ptr); +@JS("wasm_bindgen.drop_dart_object") +// ignore: non_constant_identifier_names +external void dropDartObject(int ptr); + +abstract class FlutterRustBridgeWireBase { + void storeDartPostCObject() {} + // ignore: non_constant_identifier_names + void free_WireSyncReturn(WireSyncReturn raw) {} + + // ignore: non_constant_identifier_names + Object get_dart_object(int ptr) { + return getDartObject(ptr); + } + + // ignore: non_constant_identifier_names + void drop_dart_object(int ptr) { + dropDartObject(ptr); + } + + // ignore: non_constant_identifier_names + int new_dart_opaque(Object obj, NativePortType port) { + throw UnimplementedError(); + } +} + +typedef WireSyncReturn = List; + +List wireSyncReturnIntoDart(WireSyncReturn syncReturn) => syncReturn; + +class FlutterRustBridgeWasmWireBase + extends FlutterRustBridgeWireBase { + final Future init; + + FlutterRustBridgeWasmWireBase(FutureOr module) + : init = Future.value(module).then((module) => promiseToFuture(module())); +} + +typedef PlatformPointer = int; +typedef OpaqueTypeFinalizer = Finalizer; + +/// An opaque pointer to a Rust type. +/// Recipients of this type should call [dispose] at least once during runtime. +/// If passed to a native function after being [dispose]d, an exception will be thrown. +class FrbOpaqueBase { + static PlatformPointer initPtr(int ptr) => ptr; + static PlatformPointer nullPtr() => 0; + static bool isStalePtr(PlatformPointer ptr) => ptr == 0; + static void finalizerAttach(FrbOpaqueBase opaque, PlatformPointer ptr, int _, + OpaqueTypeFinalizer finalizer) => + finalizer.attach(opaque, ptr, detach: opaque); +} diff --git a/lib/src/bridge_engine/helpers.dart b/lib/src/bridge_engine/helpers.dart new file mode 100644 index 00000000..77d9389e --- /dev/null +++ b/lib/src/bridge_engine/helpers.dart @@ -0,0 +1,143 @@ +import 'dart:async'; + +import 'basic.dart'; +import 'platform_independent.dart'; +export 'ffi.dart'; + +/// borrowed from flutter foundation [kIsWeb](https://api.flutter.dev/flutter/foundation/kIsWeb-constant.html), +/// but allows for using it in a Dart context alike +const bool kIsWeb = identical(0, 0.0); + +const uuidSizeInBytes = 16; + +/// Allow custom setup hooks before ffi can be executed. +/// All other ffi calls will wait (async) until the setup ffi finishes. +/// +/// Usage: +/// +/// 1. Please call [setupMixinConstructor] inside the constructor of your class. +/// 2. Inside your [setup], please call ffi functions with hint=[kHintSetup]. +mixin FlutterRustBridgeSetupMixin + on FlutterRustBridgeBase { + /// Inside your [setup], please call ffi functions with hint=[kHintSetup]. + static const kHintSetup = _FlutterRustBridgeSetupMixinSkipWaitHint._(); + + final _setupCompleter = Completer(); + + /// Please call it inside the constructor of your class. + void setupMixinConstructor() { + () async { + try { + log('FlutterRustBridgeSetupMixin.setupMixinConstructor start setup'); + await setup(); + } finally { + log('FlutterRustBridgeSetupMixin.setupMixinConstructor complete setup'); + _setupCompleter.complete(); + } + }(); + } + + @override + Future executeNormal(FlutterRustBridgeTask task) async { + await _beforeExecute(task); + return await super.executeNormal(task); + } + + @override + Stream executeStream(FlutterRustBridgeTask task) async* { + await _beforeExecute(task); + yield* super.executeStream(task); + } + + Future _beforeExecute(FlutterRustBridgeTask task) async { + if (!_setupCompleter.isCompleted && + task.hint is! _FlutterRustBridgeSetupMixinSkipWaitHint) { + log('FlutterRustBridgeSetupMixin.beforeExecute start waiting setup to complete (task=${task.debugName})'); + await _setupCompleter.future; + log('FlutterRustBridgeSetupMixin.beforeExecute end waiting setup to complete (task=${task.debugName})'); + } + } + + /// Do your setup logic inside this function. + + Future setup(); + + /// Configure a logger for error handling. + + void log(String message) {} +} + +class _FlutterRustBridgeSetupMixinSkipWaitHint { + const _FlutterRustBridgeSetupMixinSkipWaitHint._(); +} + +/// Add a timeout to [executeNormal] +mixin FlutterRustBridgeTimeoutMixin + on FlutterRustBridgeBase { + @override + Future executeNormal(FlutterRustBridgeTask task) { + // capture a stack trace at *here*, such that when timeout, can have a good stack trace + final stackTrace = StackTrace.current; + + final timeLimitForExecuteNormal = this.timeLimitForExecuteNormal; + + var future = super.executeNormal(task); + if (timeLimitForExecuteNormal != null) { + future = future.timeout(timeLimitForExecuteNormal, + onTimeout: () => throw FlutterRustBridgeTimeoutException( + timeLimitForExecuteNormal, task.debugName, stackTrace)); + } + + return future; + } + + /// The time limit for methods using [executeNormal]. Return null means *disable* this functionality. + + Duration? get timeLimitForExecuteNormal; +} + +/// Thrown when the browser is not run in a [cross-origin isolated] environment. +/// +/// [cross-origin isolated]: https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated +class MissingHeaderException implements Exception { + const MissingHeaderException(); + static const _message = ''' +Buffers cannot be shared due to missing cross-origin headers. +Make sure your web server responds with the following headers: +- cross-origin-opener-policy: same-origin +- cross-origin-embedder-policy: require-corp'''; + + @override + String toString() => _message; +} + +class PlatformMismatchException implements Exception { + const PlatformMismatchException(); + static const _wasm = 'Not implemented on non-WASM platforms'; + + @override + String toString() => _wasm; +} + +class UnmodifiableTypedListException implements Exception { + const UnmodifiableTypedListException(); + + static const _message = 'Cannot modify the length of typed lists.'; + + @override + String toString() => _message; +} + +DateTime wire2apiTimestamp({required int ts, required bool isUtc}) { + if (kIsWeb) { + return DateTime.fromMillisecondsSinceEpoch(ts, isUtc: isUtc); + } + return DateTime.fromMicrosecondsSinceEpoch(ts, isUtc: isUtc); +} + +Duration wire2apiDuration(int ts) { + if (kIsWeb) { + return Duration(milliseconds: ts); + } + return Duration(microseconds: ts); +} diff --git a/lib/src/bridge_engine/isolate.dart b/lib/src/bridge_engine/isolate.dart new file mode 100644 index 00000000..d2300eb8 --- /dev/null +++ b/lib/src/bridge_engine/isolate.dart @@ -0,0 +1,3 @@ +export 'isolate/stub.dart' + if (dart.library.io) 'isolate/io.dart' + if (dart.library.html) 'isolate/web.dart'; diff --git a/lib/src/bridge_engine/isolate/io.dart b/lib/src/bridge_engine/isolate/io.dart new file mode 100644 index 00000000..d786b68c --- /dev/null +++ b/lib/src/bridge_engine/isolate/io.dart @@ -0,0 +1,4 @@ +import 'dart:isolate'; +export 'dart:isolate'; + +ReceivePort broadcastPort(String channelName) => ReceivePort(channelName); diff --git a/lib/src/bridge_engine/isolate/stub.dart b/lib/src/bridge_engine/isolate/stub.dart new file mode 100644 index 00000000..ce6bdc28 --- /dev/null +++ b/lib/src/bridge_engine/isolate/stub.dart @@ -0,0 +1,4 @@ +import 'dart:isolate'; +export 'dart:isolate'; + +ReceivePort broadcastPort(String portName) => throw UnimplementedError(); diff --git a/lib/src/bridge_engine/isolate/web.dart b/lib/src/bridge_engine/isolate/web.dart new file mode 100644 index 00000000..b19e846c --- /dev/null +++ b/lib/src/bridge_engine/isolate/web.dart @@ -0,0 +1,163 @@ +// ignore_for_file: avoid_web_libraries_in_flutter + +/// Shims for dart:isolate on the web. +library html_isolate; + +import 'dart:async'; +import 'dart:html' as html; +import 'dart:html' hide MessagePort; + +import 'package:rust_in_flutter/src/bridge_engine/ffi/web.dart'; + +typedef MessagePort = PortLike; + +/// An alias to [MessagePort] on web platforms. +typedef SendPort = PortLike; + +typedef NativePortType = dynamic; + +abstract class Channel { + SendPort get sendPort; + SendPort get receivePort; + const Channel(); + factory Channel.messageChannel() = _MessageChannelWrapper; + factory Channel.broadcastChannel(String channelName) = + _BroadcastChannelWrapper; +} + +class _MessageChannelWrapper implements Channel { + final channel = MessageChannel(); + @override + SendPort get sendPort => PortLike.messagePort(channel.port2); + @override + SendPort get receivePort => PortLike.messagePort(channel.port1); +} + +class _BroadcastChannelWrapper implements Channel { + final BroadcastChannel channel; + _BroadcastChannelWrapper(String channelName) + : channel = BroadcastChannel(channelName); + @override + SendPort get sendPort => PortLike.broadcastChannel(channel); + @override + SendPort get receivePort => sendPort; +} + +/// Wrapper around a [MessageChannel]. +class RawReceivePort { + /// The underlying message channel. + final Channel channel; + RawReceivePort([Channel? channel]) + : channel = channel ?? Channel.messageChannel(); + + set handler(Function(dynamic) handler) { + receivePort.onMessage.listen((event) => handler(event.data)); + } + + /// Close the receive port. + void close() => channel.receivePort.close(); + + /// The port to be used by other workers. + SendPort get sendPort => channel.sendPort; + + /// The port used to receive messages from other workers. + SendPort get receivePort => channel.receivePort; +} + +/// Web implementation of the `dart:isolate`'s ReceivePort. +class ReceivePort extends Stream { + /// The receive port. + final RawReceivePort port; + + static dynamic _extractData(MessageEvent event) => event.data; + + /// Create a new receive port from an optional [RawReceivePort]. + ReceivePort([RawReceivePort? port]) : port = port ?? RawReceivePort(); + + @override + StreamSubscription listen( + void Function(dynamic event)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + return port.receivePort.onMessage.map(_extractData).listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } + + /// The send port. + SendPort get sendPort => port.sendPort; + + /// Close the receive port, ignoring any further messages. + void close() => port.receivePort.close(); +} + +ReceivePort broadcastPort(String channelName) => + ReceivePort(RawReceivePort(Channel.broadcastChannel(channelName))); + +/// [html.MessagePort]'s interface. +abstract class PortLike extends EventTarget { + factory PortLike.messagePort(html.MessagePort port) = _MessagePortWrapper; + factory PortLike.broadcastChannel(BroadcastChannel channel) = + _BroadcastPortWrapper; + void postMessage(Object? value); + void close(); + NativePortType get nativePort; +} + +/// Delegates a subset of PortLike methods verbatim. +abstract class _DelegatedPort implements PortLike { + @override + void addEventListener(String type, html.EventListener? listener, + [bool? useCapture]) => + nativePort.addEventListener(type, listener, useCapture); + + @override + void removeEventListener(String type, html.EventListener? listener, + [bool? useCapture]) => + nativePort.removeEventListener(type, listener, useCapture); + + @override + void close() => nativePort.close(); + + @override + bool dispatchEvent(html.Event event) => nativePort.dispatchEvent(event); + + @override + html.Events get on => nativePort.on; +} + +class _MessagePortWrapper extends _DelegatedPort { + @override + final html.MessagePort nativePort; + _MessagePortWrapper(this.nativePort); + + @override + void postMessage(message, [List? transfer]) => + nativePort.postMessage(message, transfer); +} + +class _BroadcastPortWrapper extends _DelegatedPort { + @override + final html.BroadcastChannel nativePort; + _BroadcastPortWrapper(this.nativePort); + + /// This presents a limitation of BroadcastChannel, + /// i.e. it cannot carry transferables and will unconditionally clone the items. + @override + void postMessage(message, [List? transfer]) { + if (transfer != null && transfer.isNotEmpty) { + warn("Ignoring transferables for BroadcastPort:", transfer); + } + nativePort.postMessage(message ?? false); + } +} + +extension on PortLike { + static const messageEvent = EventStreamProvider('message'); + Stream get onMessage => messageEvent.forTarget(this); +} diff --git a/lib/src/bridge_engine/load.dart b/lib/src/bridge_engine/load.dart new file mode 100644 index 00000000..0c8ac525 --- /dev/null +++ b/lib/src/bridge_engine/load.dart @@ -0,0 +1 @@ +export 'load/load.io.dart' if (dart.library.html) 'load/load.web.dart'; diff --git a/lib/src/bridge_engine/load/load.io.dart b/lib/src/bridge_engine/load/load.io.dart new file mode 100644 index 00000000..72876259 --- /dev/null +++ b/lib/src/bridge_engine/load/load.io.dart @@ -0,0 +1,11 @@ +import 'dart:ffi'; +import 'dart:io'; + +/// agnostic dynamic library loading on native platforms +DynamicLibrary loadLibForFlutter(String path) => Platform.isIOS + ? DynamicLibrary.process() + : Platform.isMacOS && Abi.current() == Abi.macosX64 + ? DynamicLibrary.executable() + : DynamicLibrary.open(path); +DynamicLibrary loadLibForDart(String path) => + Platform.isIOS ? DynamicLibrary.process() : DynamicLibrary.open(path); diff --git a/lib/src/bridge_engine/load/load.web.dart b/lib/src/bridge_engine/load/load.web.dart new file mode 100644 index 00000000..78206067 --- /dev/null +++ b/lib/src/bridge_engine/load/load.web.dart @@ -0,0 +1,2 @@ +// there's no dynamic library loading on web +// this file is just used for conditional import purpose diff --git a/lib/src/bridge_engine/platform_independent.dart b/lib/src/bridge_engine/platform_independent.dart new file mode 100644 index 00000000..ad8e5e37 --- /dev/null +++ b/lib/src/bridge_engine/platform_independent.dart @@ -0,0 +1,113 @@ +/// Base class for various kinds of tasks. +/// Note: Normally you do not manually create instances of this task (or its brothers), but instead +/// it is generated automatically by the codegen. + +abstract class FlutterRustBridgeBaseTask { + /// Metadata that does not change across different method calls. + final FlutterRustBridgeTaskConstMeta constMeta; + + /// Arguments to be passed into the function call. + final List argValues; + + /// Transparent hint given by the caller of the method + final dynamic hint; + + /// Create a new task. + const FlutterRustBridgeBaseTask({ + required this.constMeta, + required this.argValues, + required this.hint, + }); + + /// Name usually used for logging or debugging + String get debugName => constMeta.debugName; + + /// Arguments to be passed into the function call, provided in the format of a series of [MapEntry]s + Iterable> get argMapEntries sync* { + for (var i = 0; i < constMeta.argNames.length; ++i) { + yield MapEntry(constMeta.argNames[i], argValues[i]); + } + } + + /// Arguments to be passed into the function call, provided in the format of a [Map] + Map get argMap => Map.fromEntries(argMapEntries); +} + +/// Metadata that does not change across different method calls. Thus it is made `const` to save memory and speed up + +class FlutterRustBridgeTaskConstMeta { + /// Used for debugging purposes only. + final String debugName; + + /// A list of arguments to the task. + final List argNames; + + /// Create a new task metadata. + const FlutterRustBridgeTaskConstMeta({ + required this.debugName, + required this.argNames, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FlutterRustBridgeTaskConstMeta && + runtimeType == other.runtimeType && + debugName == other.debugName && + _listEquals(argNames, other.argNames); + + @override + int get hashCode => debugName.hashCode ^ Object.hashAll(argNames); + + @override + String toString() => + 'FlutterRustBridgeTaskConstMeta{debugName: $debugName, argNames: $argNames}'; +} + +/// An exception that is generated by Rust code. + +class FfiException { + /// The error code of the exception. + final String code; + + /// The error message of the exception. + final String message; + + /// The error details of the exception. Optional. + final Object? details; + + const FfiException(this.code, this.message, [this.details]); + + @override + String toString() => 'FfiException($code, $message, $details)'; +} + +/// Exception when timeout happens using [FlutterRustBridgeTimeoutMixin] + +class FlutterRustBridgeTimeoutException { + /// The duration to trigger timeout + final Duration duration; + + /// debugName of the task, usually the ffi function name + final String debugName; + + /// The stack trace of the error + final StackTrace stackTrace; + + const FlutterRustBridgeTimeoutException( + this.duration, this.debugName, this.stackTrace); + + @override + String toString() => + 'FlutterRustBridgeTimeoutException(debugName=$debugName, duration=$duration, stackTrace=$stackTrace)'; +} + +bool _listEquals(List? a, List? b) { + if (a == null) return b == null; + if (b == null || a.length != b.length) return false; + if (identical(a, b)) return true; + for (var index = 0; index < a.length; index += 1) { + if (a[index] != b[index]) return false; + } + return true; +} diff --git a/lib/src/bridge_engine/typed_data.dart b/lib/src/bridge_engine/typed_data.dart new file mode 100644 index 00000000..e40d3906 --- /dev/null +++ b/lib/src/bridge_engine/typed_data.dart @@ -0,0 +1 @@ +export 'typed_data/io.dart' if (dart.library.html) 'typed_data/web.dart'; diff --git a/lib/src/bridge_engine/typed_data/io.dart b/lib/src/bridge_engine/typed_data/io.dart new file mode 100644 index 00000000..4222ea9f --- /dev/null +++ b/lib/src/bridge_engine/typed_data/io.dart @@ -0,0 +1,113 @@ +export 'dart:typed_data' hide Int64List, Uint64List; +import 'dart:collection'; +import 'dart:typed_data' as $data; + +import 'package:rust_in_flutter/src/bridge_engine/exports.dart'; + +abstract class _TypedList extends ListMixin { + List get inner; + + @override + _TypedList operator +(Object other); + + T raw2dart(int value); + int dart2raw(dynamic value); + + @override + T operator [](int index) => raw2dart(inner[index]); + + @override + void operator []=(int index, dynamic value) => inner[index] = dart2raw(value); + + @override + int get length => inner.length; + + @override + set length(int newLength) => throw const UnmodifiableTypedListException(); +} + +/// A strict version of [$data.Int64List] which always returns a [BigInt]. +class Int64List extends _TypedList { + @override + final $data.Int64List inner; + Int64List.from(this.inner); + factory Int64List(int length) => Int64List.from($data.Int64List(length)); + Int64List.fromList(List ints) : inner = $data.Int64List.fromList(ints); + Int64List.view($data.ByteBuffer buffer, [int offsetInBytes = 0, int? length]) + : inner = $data.Int64List.view(buffer, offsetInBytes, length); + Int64List.sublistView($data.TypedData data, [int start = 0, int? end]) + : inner = $data.Int64List.sublistView(data, start, end); + + @override + int dart2raw(value) { + if (value is BigInt) return value.toInt(); + if (value is int) return value; + throw ArgumentError.value(value); + } + + @override + BigInt raw2dart(int value) => BigInt.from(value); + + @override + Int64List operator +(Object other) { + if (other is Int64List) return Int64List.fromList(inner + other.inner); + if (other is $data.Int64List) return Int64List.fromList(inner + other); + if (other is List) return Int64List.fromList(inner + other); + if (other is Iterable) { + return Int64List.fromList(inner + other.toList(growable: false)); + } + throw ArgumentError.value(other); + } +} + +/// A strict version of [$data.Uint64List] which always returns a [BigInt]. +class Uint64List extends _TypedList { + @override + final $data.Uint64List inner; + Uint64List.from(this.inner); + factory Uint64List(int length) => Uint64List.from($data.Uint64List(length)); + Uint64List.fromList(List ints) : inner = $data.Uint64List.fromList(ints); + Uint64List.view($data.ByteBuffer buffer, [int offsetInBytes = 0, int? length]) + : inner = $data.Uint64List.view(buffer, offsetInBytes, length); + Uint64List.sublistView($data.TypedData data, [int start = 0, int? end]) + : inner = $data.Uint64List.sublistView(data, start, end); + + static final _maxI64b = BigInt.from(0x7FFFFFFFFFFFFFFF); + static const _minI64 = 0x8000000000000000; + + @override + BigInt raw2dart(int value) { + if (value < 0) { + // two's complement signed integer to unsigned bigint + return _maxI64b + BigInt.from(value - _minI64) + BigInt.one; + } + return BigInt.from(value); + } + + @override + int dart2raw(value) { + if (value is int) return value; + if (value is BigInt) { + if (value > _maxI64b) { + // unsigned bigint (64 bits) to two's complement signed integer + value -= _maxI64b; + value -= BigInt.one; + return value.toInt() + _minI64; + } else { + return value.toInt(); + } + } + throw ArgumentError.value(value); + } + + @override + Uint64List operator +(Object other) { + if (other is Uint64List) return Uint64List.fromList(inner + other.inner); + if (other is $data.Uint64List) return Uint64List.fromList(inner + other); + if (other is List) return Uint64List.fromList(inner + other); + if (other is Iterable) { + return Uint64List.fromList(inner + other.toList(growable: false)); + } + throw ArgumentError.value(other); + } +} diff --git a/lib/src/bridge_engine/typed_data/web.dart b/lib/src/bridge_engine/typed_data/web.dart new file mode 100644 index 00000000..f9e45966 --- /dev/null +++ b/lib/src/bridge_engine/typed_data/web.dart @@ -0,0 +1,149 @@ +@JS() +library html_typed_data; + +import 'dart:collection'; +import 'package:js/js.dart'; +import 'package:js/js_util.dart'; + +import 'dart:typed_data' hide Int64List, Uint64List; + +import 'package:rust_in_flutter/src/bridge_engine/helpers.dart' + show UnmodifiableTypedListException; +export 'dart:typed_data' hide Int64List, Uint64List; + +@JS('TypedArray') +abstract class TypedArray { + external ByteBuffer get buffer; + external int length; + external BigInt at(int index); +} + +extension on TypedArray { + operator []=(int index, value) { + setProperty(this, index, value); + } +} + +@JS('BigInt64Array') +abstract class BigInt64Array extends TypedArray { + external factory BigInt64Array(Object lengthOrBuffer, + [int? offset, int? length]); + + factory BigInt64Array.fromList(List list) => + BigInt64Array(list.map((n) => BigInt.from(n)).toList()); + + factory BigInt64Array.view( + ByteBuffer buffer, [ + int offset = 0, + int? length, + ]) => + BigInt64Array(buffer, offset, length); + + factory BigInt64Array.sublistView(TypedData array, + [int offset = 0, int? length]) => + BigInt64Array(array.buffer, offset, length); +} + +@JS('BigUint64Array') +abstract class BigUint64Array extends TypedArray { + external factory BigUint64Array(Object lengthOrBuffer, + [int? offset, int? buffer]); + + factory BigUint64Array.fromList(List list) => + BigUint64Array(list.map((n) => BigInt.from(n)).toList()); + + factory BigUint64Array.view(ByteBuffer buffer, + [int offset = 0, int? length]) => + BigUint64Array(buffer, offset, length); + + factory BigUint64Array.sublistView(TypedData array, + [int offset = 0, int? length]) => + BigUint64Array(array.buffer, offset, length); +} + +/// Opt out of type safety for setting the value. +/// Helpful if the array needs to accept multiple types. +abstract class _SetAnyListMixin extends ListMixin { + @override + void operator []=(int index, dynamic value) { + this[index] = value; + } +} + +abstract class TypedList extends _SetAnyListMixin { + TypedArray get inner; + + /// How to cast a raw JS value to an acceptable Dart value. + T js2dart(Object? value); + + /// How to convert a Dart integer-like value to an acceptable JS value. + dynamic dart2js(Object? value); + + @override + T operator [](int index) => js2dart(inner.at(index)); + + @override + void operator []=(int index, value) { + inner[index] = dart2js(value); + } + + @override + int get length => inner.length; + + @override + set length(int newLength) => throw UnmodifiableTypedListException(); + + ByteBuffer get buffer => inner.buffer; +} + +BigInt _castBigInt(Object bigInt) { + return BigInt.parse(callMethod(bigInt, 'toString', const [])); +} + +Object _convertBigInt(Object dart) { + if (dart is int) return BigInt.from(dart); + // Assume value is already JS safe. + return dart; +} + +class Int64List extends TypedList { + @override + final BigInt64Array inner; + Int64List.from(this.inner); + + @override + BigInt js2dart(Object? value) => _castBigInt(value!); + + @override + dart2js(Object? value) => _convertBigInt(value!); + + factory Int64List(int length) => Int64List.from(BigInt64Array(length)); + factory Int64List.fromList(List list) => + Int64List.from(BigInt64Array.fromList(list)); + factory Int64List.view(ByteBuffer buffer, [int offset = 0, int? length]) => + Int64List.from(BigInt64Array.view(buffer, offset, length)); + factory Int64List.sublistView(TypedData array, + [int offset = 0, int? length]) => + Int64List.from(BigInt64Array.sublistView(array, offset, length)); +} + +class Uint64List extends TypedList { + @override + final BigUint64Array inner; + Uint64List.from(this.inner); + + @override + BigInt js2dart(Object? value) => _castBigInt(value!); + + @override + dart2js(Object? value) => _convertBigInt(value!); + + factory Uint64List(int length) => Uint64List.from(BigUint64Array(length)); + factory Uint64List.fromList(List list) => + Uint64List.from(BigUint64Array.fromList(list)); + factory Uint64List.view(ByteBuffer buffer, [int offset = 0, int? length]) => + Uint64List.from(BigUint64Array.view(buffer, offset, length)); + factory Uint64List.sublistView(TypedData array, + [int offset = 0, int? length]) => + Uint64List.from(BigUint64Array.sublistView(array, offset, length)); +} diff --git a/lib/src/bridge_engine/utils.dart b/lib/src/bridge_engine/utils.dart new file mode 100644 index 00000000..79c3759d --- /dev/null +++ b/lib/src/bridge_engine/utils.dart @@ -0,0 +1,111 @@ +import 'dart:async'; +import 'isolate.dart'; + +// NOTE XXX copy from: https://github.com/dart-archive/isolate/blob/master/lib/ports.dart +// Because [package:isolate] is not maintained anymore, so the code is copied and maintained by ourselves. + +/// Create a [SendPort] that accepts only one message. +/// +/// When the first message is received, the [callback] function is +/// called with the message as argument, +/// and the [completer] is completed with the result of that call. +/// All further messages are ignored. +/// +/// If `callback` is omitted, it defaults to an identity function. +/// The `callback` call may return a future, and the completer will +/// wait for that future to complete. If [callback] is omitted, the +/// message on the port must be an instance of [R]. +/// +/// If [timeout] is supplied, it is used as a limit on how +/// long it can take before the message is received. If a +/// message isn't received in time, the [onTimeout] is called, +/// and `completer` is completed with the result of that call +/// instead. +/// The [callback] function will not be interrupted by the time-out, +/// as long as the initial message is received in time. +/// If `onTimeout` is omitted, it defaults to completing the `completer` with +/// a [TimeoutException]. +/// +/// The [completer] may be a synchronous completer. It is only +/// completed in response to another event, either a port message or a timer. +/// +/// Returns the `SendPort` expecting the single message. +SendPort singleCompletePort( + Completer completer, { + FutureOr Function(P message)? callback, + Duration? timeout, + FutureOr Function()? onTimeout, +}) { + if (callback == null && timeout == null) { + return _singleCallbackPort((response) { + _castComplete(completer, response); + }); + } + var responsePort = RawReceivePort(); + Timer? timer; + if (callback == null) { + responsePort.handler = (response) { + responsePort.close(); + timer?.cancel(); + _castComplete(completer, response); + }; + } else { + var zone = Zone.current; + var action = zone.registerUnaryCallback((response) { + try { + // Also catch it if callback throws. + completer.complete(callback(response as P)); + } catch (error, stack) { + completer.completeError(error, stack); + } + }); + responsePort.handler = (response) { + responsePort.close(); + timer?.cancel(); + zone.runUnary(action, response as P); + }; + } + if (timeout != null) { + timer = Timer(timeout, () { + responsePort.close(); + if (onTimeout != null) { + /// workaround for incomplete generic parameters promotion. + /// example is available in 'TimeoutFirst with invalid null' test + try { + completer.complete(Future.sync(onTimeout)); + } catch (e, st) { + completer.completeError(e, st); + } + } else { + completer + .completeError(TimeoutException('Future not completed', timeout)); + } + }); + } + return responsePort.sendPort; +} + +/// Helper function for [singleCallbackPort]. +/// +/// Replace [singleCallbackPort] with this +/// when removing the deprecated parameters. +SendPort _singleCallbackPort

(void Function(P) callback) { + var responsePort = RawReceivePort(); + var zone = Zone.current; + callback = zone.registerUnaryCallback(callback); + responsePort.handler = (response) { + responsePort.close(); + zone.runUnary(callback, response as P); + }; + return responsePort.sendPort; +} + +// Helper function that casts an object to a type and completes a +// corresponding completer, or completes with the error if the cast fails. +void _castComplete(Completer completer, Object? value) { + try { + completer.complete(value as R); + } catch (error, stack) { + completer.completeError(error, stack); + } +} diff --git a/lib/src/bridge_generated.dart b/lib/src/bridge_generated.dart new file mode 100644 index 00000000..bbed5756 --- /dev/null +++ b/lib/src/bridge_generated.dart @@ -0,0 +1,199 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import "bridge_definitions.dart"; +import 'dart:convert'; +import 'dart:async'; + +import 'bridge_engine/exports.dart'; +import 'bridge_generated.io.dart' + if (dart.library.html) 'bridge_generated.web.dart'; + +class BridgeImpl implements Bridge { + final BridgePlatform _platform; + factory BridgeImpl(ExternalLibrary dylib) => + BridgeImpl.raw(BridgePlatform(dylib)); + + /// Only valid on web/WASM platforms. + factory BridgeImpl.wasm(FutureOr module) => + BridgeImpl(module as ExternalLibrary); + BridgeImpl.raw(this._platform); + Stream prepareRustSignalStream({dynamic hint}) { + return _platform.executeStream(FlutterRustBridgeTask( + callFfi: (port_) => + _platform.inner.wire_prepare_rust_signal_stream(port_), + parseSuccessData: _wire2api_rust_signal, + constMeta: kPrepareRustSignalStreamConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrepareRustSignalStreamConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "prepare_rust_signal_stream", + argNames: [], + ); + + Stream prepareRustResponseStream({dynamic hint}) { + return _platform.executeStream(FlutterRustBridgeTask( + callFfi: (port_) => + _platform.inner.wire_prepare_rust_response_stream(port_), + parseSuccessData: _wire2api_rust_response_unique, + constMeta: kPrepareRustResponseStreamConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrepareRustResponseStreamConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "prepare_rust_response_stream", + argNames: [], + ); + + Future prepareChannels({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_prepare_channels(port_), + parseSuccessData: _wire2api_unit, + constMeta: kPrepareChannelsConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrepareChannelsConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "prepare_channels", + argNames: [], + ); + + Future checkRustStreams({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_check_rust_streams(port_), + parseSuccessData: _wire2api_bool, + constMeta: kCheckRustStreamsConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kCheckRustStreamsConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "check_rust_streams", + argNames: [], + ); + + Future startRustLogic({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_start_rust_logic(port_), + parseSuccessData: _wire2api_unit, + constMeta: kStartRustLogicConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kStartRustLogicConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "start_rust_logic", + argNames: [], + ); + + Future requestToRust( + {required RustRequestUnique requestUnique, dynamic hint}) { + var arg0 = + _platform.api2wire_box_autoadd_rust_request_unique(requestUnique); + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_request_to_rust(port_, arg0), + parseSuccessData: _wire2api_unit, + constMeta: kRequestToRustConstMeta, + argValues: [requestUnique], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kRequestToRustConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "request_to_rust", + argNames: ["requestUnique"], + ); + + void dispose() { + _platform.dispose(); + } +// Section: wire2api + + bool _wire2api_bool(dynamic raw) { + return raw as bool; + } + + int _wire2api_i32(dynamic raw) { + return raw as int; + } + + Uint8List? _wire2api_opt_uint_8_list(dynamic raw) { + return raw == null ? null : _wire2api_uint_8_list(raw); + } + + RustResponse _wire2api_rust_response(dynamic raw) { + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return RustResponse( + successful: _wire2api_bool(arr[0]), + message: _wire2api_opt_uint_8_list(arr[1]), + blob: _wire2api_opt_uint_8_list(arr[2]), + ); + } + + RustResponseUnique _wire2api_rust_response_unique(dynamic raw) { + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return RustResponseUnique( + id: _wire2api_i32(arr[0]), + response: _wire2api_rust_response(arr[1]), + ); + } + + RustSignal _wire2api_rust_signal(dynamic raw) { + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return RustSignal( + resource: _wire2api_i32(arr[0]), + message: _wire2api_opt_uint_8_list(arr[1]), + blob: _wire2api_opt_uint_8_list(arr[2]), + ); + } + + int _wire2api_u8(dynamic raw) { + return raw as int; + } + + Uint8List _wire2api_uint_8_list(dynamic raw) { + return raw as Uint8List; + } + + void _wire2api_unit(dynamic raw) { + return; + } +} + +// Section: api2wire + +int api2wire_i32(int raw) { + return raw; +} + +int api2wire_rust_operation(RustOperation raw) { + return api2wire_i32(raw.index); +} + +int api2wire_u8(int raw) { + return raw; +} + +// Section: finalizer diff --git a/lib/src/bridge_generated.io.dart b/lib/src/bridge_generated.io.dart new file mode 100644 index 00000000..c510e85d --- /dev/null +++ b/lib/src/bridge_generated.io.dart @@ -0,0 +1,331 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import "bridge_definitions.dart"; +import 'dart:convert'; +import 'dart:async'; + +import 'bridge_engine/exports.dart'; +import 'bridge_generated.dart'; +export 'bridge_generated.dart'; +import 'dart:ffi' as ffi; + +class BridgePlatform extends FlutterRustBridgeBase { + BridgePlatform(ffi.DynamicLibrary dylib) : super(BridgeWire(dylib)); + +// Section: api2wire + + ffi.Pointer api2wire_box_autoadd_rust_request_unique( + RustRequestUnique raw) { + final ptr = inner.new_box_autoadd_rust_request_unique_0(); + _api_fill_to_wire_rust_request_unique(raw, ptr.ref); + return ptr; + } + + ffi.Pointer api2wire_opt_uint_8_list(Uint8List? raw) { + return raw == null ? ffi.nullptr : api2wire_uint_8_list(raw); + } + + ffi.Pointer api2wire_uint_8_list(Uint8List raw) { + final ans = inner.new_uint_8_list_0(raw.length); + ans.ref.ptr.asTypedList(raw.length).setAll(0, raw); + return ans; + } +// Section: finalizer + +// Section: api_fill_to_wire + + void _api_fill_to_wire_box_autoadd_rust_request_unique( + RustRequestUnique apiObj, ffi.Pointer wireObj) { + _api_fill_to_wire_rust_request_unique(apiObj, wireObj.ref); + } + + void _api_fill_to_wire_rust_request( + RustRequest apiObj, wire_RustRequest wireObj) { + wireObj.resource = api2wire_i32(apiObj.resource); + wireObj.operation = api2wire_rust_operation(apiObj.operation); + wireObj.message = api2wire_opt_uint_8_list(apiObj.message); + wireObj.blob = api2wire_opt_uint_8_list(apiObj.blob); + } + + void _api_fill_to_wire_rust_request_unique( + RustRequestUnique apiObj, wire_RustRequestUnique wireObj) { + wireObj.id = api2wire_i32(apiObj.id); + _api_fill_to_wire_rust_request(apiObj.request, wireObj.request); + } +} + +// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint + +/// generated by flutter_rust_bridge_codegen +class BridgeWire implements FlutterRustBridgeWireBase { + late final dartApi = DartApiDl(init_frb_dart_api_dl); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + BridgeWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + BridgeWire.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + void error( + ffi.Pointer msg, + ) { + return _error( + msg, + ); + } + + late final _errorPtr = + _lookup)>>( + 'error'); + late final _error = + _errorPtr.asFunction)>(); + + int new_dart_opaque( + Object handle, + ) { + return _new_dart_opaque( + handle, + ); + } + + late final _new_dart_opaquePtr = + _lookup>( + 'new_dart_opaque'); + late final _new_dart_opaque = + _new_dart_opaquePtr.asFunction(); + + Object get_dart_object( + int ptr, + ) { + return _get_dart_object( + ptr, + ); + } + + late final _get_dart_objectPtr = + _lookup>( + 'get_dart_object'); + late final _get_dart_object = + _get_dart_objectPtr.asFunction(); + + void drop_dart_object( + int ptr, + ) { + return _drop_dart_object( + ptr, + ); + } + + late final _drop_dart_objectPtr = + _lookup>( + 'drop_dart_object'); + late final _drop_dart_object = + _drop_dart_objectPtr.asFunction(); + + int init_frb_dart_api_dl( + ffi.Pointer data, + ) { + return _init_frb_dart_api_dl( + data, + ); + } + + late final _init_frb_dart_api_dlPtr = + _lookup)>>( + 'init_frb_dart_api_dl'); + late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr + .asFunction)>(); + + void store_dart_post_cobject( + DartPostCObjectFnType ptr, + ) { + return _store_dart_post_cobject( + ptr, + ); + } + + late final _store_dart_post_cobjectPtr = + _lookup>( + 'store_dart_post_cobject'); + late final _store_dart_post_cobject = _store_dart_post_cobjectPtr + .asFunction(); + + void wire_prepare_rust_signal_stream( + int port_, + ) { + return _wire_prepare_rust_signal_stream( + port_, + ); + } + + late final _wire_prepare_rust_signal_streamPtr = + _lookup>( + 'wire_prepare_rust_signal_stream'); + late final _wire_prepare_rust_signal_stream = + _wire_prepare_rust_signal_streamPtr.asFunction(); + + void wire_prepare_rust_response_stream( + int port_, + ) { + return _wire_prepare_rust_response_stream( + port_, + ); + } + + late final _wire_prepare_rust_response_streamPtr = + _lookup>( + 'wire_prepare_rust_response_stream'); + late final _wire_prepare_rust_response_stream = + _wire_prepare_rust_response_streamPtr.asFunction(); + + void wire_prepare_channels( + int port_, + ) { + return _wire_prepare_channels( + port_, + ); + } + + late final _wire_prepare_channelsPtr = + _lookup>( + 'wire_prepare_channels'); + late final _wire_prepare_channels = + _wire_prepare_channelsPtr.asFunction(); + + void wire_check_rust_streams( + int port_, + ) { + return _wire_check_rust_streams( + port_, + ); + } + + late final _wire_check_rust_streamsPtr = + _lookup>( + 'wire_check_rust_streams'); + late final _wire_check_rust_streams = + _wire_check_rust_streamsPtr.asFunction(); + + void wire_start_rust_logic( + int port_, + ) { + return _wire_start_rust_logic( + port_, + ); + } + + late final _wire_start_rust_logicPtr = + _lookup>( + 'wire_start_rust_logic'); + late final _wire_start_rust_logic = + _wire_start_rust_logicPtr.asFunction(); + + void wire_request_to_rust( + int port_, + ffi.Pointer request_unique, + ) { + return _wire_request_to_rust( + port_, + request_unique, + ); + } + + late final _wire_request_to_rustPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, + ffi.Pointer)>>('wire_request_to_rust'); + late final _wire_request_to_rust = _wire_request_to_rustPtr + .asFunction)>(); + + ffi.Pointer new_box_autoadd_rust_request_unique_0() { + return _new_box_autoadd_rust_request_unique_0(); + } + + late final _new_box_autoadd_rust_request_unique_0Ptr = _lookup< + ffi.NativeFunction Function()>>( + 'new_box_autoadd_rust_request_unique_0'); + late final _new_box_autoadd_rust_request_unique_0 = + _new_box_autoadd_rust_request_unique_0Ptr + .asFunction Function()>(); + + ffi.Pointer new_uint_8_list_0( + int len, + ) { + return _new_uint_8_list_0( + len, + ); + } + + late final _new_uint_8_list_0Ptr = _lookup< + ffi + .NativeFunction Function(ffi.Int32)>>( + 'new_uint_8_list_0'); + late final _new_uint_8_list_0 = _new_uint_8_list_0Ptr + .asFunction Function(int)>(); + + void free_WireSyncReturn( + WireSyncReturn ptr, + ) { + return _free_WireSyncReturn( + ptr, + ); + } + + late final _free_WireSyncReturnPtr = + _lookup>( + 'free_WireSyncReturn'); + late final _free_WireSyncReturn = + _free_WireSyncReturnPtr.asFunction(); +} + +final class _Dart_Handle extends ffi.Opaque {} + +final class Result_JsValue extends ffi.Opaque {} + +final class wire_uint_8_list extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + +final class wire_RustRequest extends ffi.Struct { + @ffi.Int32() + external int resource; + + @ffi.Int32() + external int operation; + + external ffi.Pointer message; + + external ffi.Pointer blob; +} + +final class wire_RustRequestUnique extends ffi.Struct { + @ffi.Int32() + external int id; + + external wire_RustRequest request; +} + +typedef DartPostCObjectFnType = ffi.Pointer< + ffi.NativeFunction< + ffi.Bool Function(DartPort port_id, ffi.Pointer message)>>; +typedef DartPort = ffi.Int64; + +const int ID = 2; diff --git a/lib/src/bridge_generated.web.dart b/lib/src/bridge_generated.web.dart new file mode 100644 index 00000000..35911221 --- /dev/null +++ b/lib/src/bridge_generated.web.dart @@ -0,0 +1,100 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import "bridge_definitions.dart"; +import 'dart:convert'; +import 'dart:async'; + +import 'bridge_engine/exports.dart'; +import 'bridge_generated.dart'; +export 'bridge_generated.dart'; + +class BridgePlatform extends FlutterRustBridgeBase + with FlutterRustBridgeSetupMixin { + BridgePlatform(FutureOr dylib) : super(BridgeWire(dylib)) { + setupMixinConstructor(); + } + Future setup() => inner.init; + +// Section: api2wire + + List api2wire_box_autoadd_rust_request_unique( + RustRequestUnique raw) { + return api2wire_rust_request_unique(raw); + } + + Uint8List? api2wire_opt_uint_8_list(Uint8List? raw) { + return raw == null ? null : api2wire_uint_8_list(raw); + } + + List api2wire_rust_request(RustRequest raw) { + return [ + api2wire_i32(raw.resource), + api2wire_rust_operation(raw.operation), + api2wire_opt_uint_8_list(raw.message), + api2wire_opt_uint_8_list(raw.blob) + ]; + } + + List api2wire_rust_request_unique(RustRequestUnique raw) { + return [api2wire_i32(raw.id), api2wire_rust_request(raw.request)]; + } + + Uint8List api2wire_uint_8_list(Uint8List raw) { + return raw; + } +// Section: finalizer +} + +// Section: WASM wire module + +@JS('wasm_bindgen') +external BridgeWasmModule get wasmModule; + +@JS() +@anonymous +class BridgeWasmModule implements WasmModule { + external Object /* Promise */ call([String? moduleName]); + external BridgeWasmModule bind(dynamic thisArg, String moduleName); + external dynamic /* void */ wire_prepare_rust_signal_stream( + NativePortType port_); + + external dynamic /* void */ wire_prepare_rust_response_stream( + NativePortType port_); + + external dynamic /* void */ wire_prepare_channels(NativePortType port_); + + external dynamic /* void */ wire_check_rust_streams(NativePortType port_); + + external dynamic /* void */ wire_start_rust_logic(NativePortType port_); + + external dynamic /* void */ wire_request_to_rust( + NativePortType port_, List request_unique); +} + +// Section: WASM wire connector + +class BridgeWire extends FlutterRustBridgeWasmWireBase { + BridgeWire(FutureOr module) + : super(WasmModule.cast(module)); + + void wire_prepare_rust_signal_stream(NativePortType port_) => + wasmModule.wire_prepare_rust_signal_stream(port_); + + void wire_prepare_rust_response_stream(NativePortType port_) => + wasmModule.wire_prepare_rust_response_stream(port_); + + void wire_prepare_channels(NativePortType port_) => + wasmModule.wire_prepare_channels(port_); + + void wire_check_rust_streams(NativePortType port_) => + wasmModule.wire_check_rust_streams(port_); + + void wire_start_rust_logic(NativePortType port_) => + wasmModule.wire_start_rust_logic(port_); + + void wire_request_to_rust( + NativePortType port_, List request_unique) => + wasmModule.wire_request_to_rust(port_, request_unique); +} diff --git a/lib/src/exports.dart b/lib/src/exports.dart new file mode 100644 index 00000000..b150f61b --- /dev/null +++ b/lib/src/exports.dart @@ -0,0 +1,2 @@ +export 'bridge_definitions.dart'; +export 'ffi.dart' if (dart.library.html) 'ffi_web.dart'; diff --git a/lib/src/ffi.dart b/lib/src/ffi.dart new file mode 100644 index 00000000..5af206a1 --- /dev/null +++ b/lib/src/ffi.dart @@ -0,0 +1,22 @@ +import 'dart:io' as io; +import 'dart:ffi'; +import 'bridge_generated.dart'; +import 'bridge_definitions.dart'; + +final Bridge api = BridgeImpl(loadNativeLibrary()); + +DynamicLibrary loadNativeLibrary() { + if (io.Platform.isLinux) { + return DynamicLibrary.open('libhub.so'); // Dynamic library + } else if (io.Platform.isAndroid) { + return DynamicLibrary.open('libhub.so'); // Dynamic library + } else if (io.Platform.isWindows) { + return DynamicLibrary.open('hub.dll'); // Dynamic library + } else if (io.Platform.isIOS) { + return DynamicLibrary.executable(); // Static library + } else if (io.Platform.isMacOS) { + return DynamicLibrary.executable(); // Static library + } else { + return DynamicLibrary.executable(); // Dummy return value + } +} diff --git a/lib/src/ffi_web.dart b/lib/src/ffi_web.dart new file mode 100644 index 00000000..f4e0fe34 --- /dev/null +++ b/lib/src/ffi_web.dart @@ -0,0 +1,6 @@ +import 'bridge_engine/exports.dart'; +import 'bridge_generated.dart'; + +final api = BridgeImpl.wasm( + WasmModule.initialize(kind: const Modules.noModules(root: 'pkg/hub')), +); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 00000000..41793633 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,27 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "rust_in_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +# Invoke the build for native code shared with the other target platforms. +# This can be changed to accommodate different builds. +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(rust_in_flutter_bundled_libraries + # Defined in ../src/CMakeLists.txt. + # This can be changed to accommodate different builds. + $ + PARENT_SCOPE +) + +include(./gen_msg.cmake) + +# Include the CMake build file of the Rust code. +include(./rust.cmake) \ No newline at end of file diff --git a/linux/gen_msg.cmake b/linux/gen_msg.cmake new file mode 100644 index 00000000..6736ebee --- /dev/null +++ b/linux/gen_msg.cmake @@ -0,0 +1,12 @@ +execute_process( + COMMAND cmd /c dart run rust_in_flutter message + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/../ + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error_output +) +if(result EQUAL 0) + message("Generate protobuf messages successfully:${output}") +else() + message(FATAL_ERROR "Generate protobuf messages failed with error code ${result}. Error output:${error_output}") +endif() \ No newline at end of file diff --git a/linux/rust.cmake b/linux/rust.cmake new file mode 100644 index 00000000..b192ac7a --- /dev/null +++ b/linux/rust.cmake @@ -0,0 +1,20 @@ +cmake_policy(SET CMP0079 NEW) +apply_standard_settings(${BINARY_NAME}) + +set_target_properties( + ${BINARY_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden + BUILD_RPATH_USE_ORIGIN ON +) + +target_compile_definitions(${BINARY_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${BINARY_NAME} ${CMAKE_SOURCE_DIR}/../native/hub hub "") + +set( + rust_in_flutter_bundled_libraries + "${${BINARY_NAME}_cargokit_lib}" + PARENT_SCOPE +) \ No newline at end of file diff --git a/macos/Classes/rust_in_flutter.c b/macos/Classes/rust_in_flutter.c new file mode 100644 index 00000000..d850f883 --- /dev/null +++ b/macos/Classes/rust_in_flutter.c @@ -0,0 +1,3 @@ +// Relative import to be able to reuse the C sources. +// See the comment in ../{projectName}}.podspec for more information. +#include "../../src/rust_in_flutter.c" diff --git a/macos/rust_in_flutter.podspec b/macos/rust_in_flutter.podspec new file mode 100644 index 00000000..f3f5102f --- /dev/null +++ b/macos/rust_in_flutter.podspec @@ -0,0 +1,51 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_in_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_in_flutter' + s.version = '0.1.0' + s.summary = 'Summary' + s.description = 'Description' + s.homepage = 'http://cunarist.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@cunarist.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.swift_version = '5.0' + + script = <<-SCRIPT + echo "Generate protobuf message" + cd $PROJECT_DIR/../../ + dart run rust_in_flutter message + echo "Build Rust library" + sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" "$PROJECT_DIR/../../native/hub" hub + SCRIPT + + # Include Rust crates in the build process. + s.script_phase = { + :name => 'Generate protobuf message and build Rust library', + :script => script, + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + :output_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony_out', '${BUILT_PRODUCTS_DIR}/output.txt'], + } + s.pod_target_xcconfig = { + # From default Flutter template. + 'DEFINES_MODULE' => 'YES', + # We use `-force_load` instead of `-l` since Xcode strips out unused symbols from static libraries. + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libhub.a', + 'DEAD_CODE_STRIPPING' => 'YES', + 'STRIP_INSTALLED_PRODUCT[config=*][sdk=*][arch=*]' => "YES", + 'STRIP_STYLE[config=*][sdk=*][arch=*]' => "non-global", + 'DEPLOYMENT_POSTPROCESSING[config=*][sdk=*][arch=*]' => "YES", + } +end diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..75b744e5 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,85 @@ +name: rust_in_flutter +description: '"Rust as your Flutter backend, Flutter as your Rust frontend"' +version: 4.4.0 +repository: https://github.com/cunarist/rust-in-flutter +funding: + - https://www.buymeacoffee.com/cunarist + +environment: + sdk: ">=3.0.5 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + package_config: ^2.1.0 + js: ^0.6.4 + +dev_dependencies: + ffigen: ^8.0.0 + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + # + # Please refer to README.md for a detailed explanation. + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + windows: + ffiPlugin: true + web: + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/shorthand/Cargo.toml b/shorthand/Cargo.toml new file mode 100644 index 00000000..fa5dd03e --- /dev/null +++ b/shorthand/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rifs" +version = "4.4.0" +edition = "2021" +license = "MIT" +description = "Rust as your Flutter backend, Flutter as your Rust frontend" +repository = "https://github.com/cunarist/rust-in-flutter" diff --git a/shorthand/README.md b/shorthand/README.md new file mode 100644 index 00000000..4bae980d --- /dev/null +++ b/shorthand/README.md @@ -0,0 +1,9 @@ +# Rust-In-Flutter Shorthand + +**"Rust as your Flutter backend, Flutter as your Rust frontend"** + +![preview](https://github.com/cunarist/rust-in-flutter/assets/66480156/be85cf04-2240-497f-8d0d-803c40536d8e) + +RIFS is a command-line tool for simplifying the use of the Rust-In-Flutter framework. For detailed information on how to use this framework, please refer to the [documentation](https://docs.cunarist.com/rust-in-flutter/). + +To feel the seamless and enjoyable experience created by the fusion of Flutter and Rust, explore the web-based [demo](https://rif-example.cunarist.com/). diff --git a/shorthand/src/main.rs b/shorthand/src/main.rs new file mode 100644 index 00000000..3f5ca695 --- /dev/null +++ b/shorthand/src/main.rs @@ -0,0 +1,20 @@ +use std::env; +use std::process::Command; + +fn main() { + // Get command-line arguments excluding the program name + let dart_command_args: Vec = env::args().skip(1).collect(); + + // Build the command to run the Dart script + #[cfg(target_family = "windows")] + let mut command = Command::new("dart.bat"); + #[cfg(target_family = "unix")] + let mut command = Command::new("dart"); + #[cfg(target_family = "wasm")] + let mut command = Command::new("dart"); + command.args(["run", "rust_in_flutter"]); + command.args(&dart_command_args); + + // Execute the command + let _ = command.status(); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..020f52e1 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,17 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +project(rust_in_flutter_library VERSION 0.0.1 LANGUAGES C) + +add_library(rust_in_flutter SHARED + "rust_in_flutter.c" +) + +set_target_properties(rust_in_flutter PROPERTIES + PUBLIC_HEADER rust_in_flutter.h + OUTPUT_NAME "rust_in_flutter" +) + +target_compile_definitions(rust_in_flutter PUBLIC DART_SHARED_LIB) diff --git a/src/rust_in_flutter.c b/src/rust_in_flutter.c new file mode 100644 index 00000000..38d2e642 --- /dev/null +++ b/src/rust_in_flutter.c @@ -0,0 +1,23 @@ +#include "rust_in_flutter.h" + +// A very short-lived native function. +// +// For very short-lived functions, it is fine to call them on the main isolate. +// They will block the Dart execution while running the native function, so +// only do this for native functions which are guaranteed to be short-lived. +FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b) { return a + b; } + +// A longer-lived native function, which occupies the thread calling it. +// +// Do not call these kind of native functions in the main isolate. They will +// block Dart execution. This will cause dropped frames in Flutter applications. +// Instead, call these native functions on a separate isolate. +FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b) { + // Simulate work. +#if _WIN32 + Sleep(5000); +#else + usleep(5000 * 1000); +#endif + return a + b; +} diff --git a/src/rust_in_flutter.h b/src/rust_in_flutter.h new file mode 100644 index 00000000..084c6422 --- /dev/null +++ b/src/rust_in_flutter.h @@ -0,0 +1,30 @@ +#include +#include +#include + +#if _WIN32 +#include +#else +#include +#include +#endif + +#if _WIN32 +#define FFI_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FFI_PLUGIN_EXPORT +#endif + +// A very short-lived native function. +// +// For very short-lived functions, it is fine to call them on the main isolate. +// They will block the Dart execution while running the native function, so +// only do this for native functions which are guaranteed to be short-lived. +FFI_PLUGIN_EXPORT intptr_t sum(intptr_t a, intptr_t b); + +// A longer lived native function, which occupies the thread calling it. +// +// Do not call these kind of native functions in the main isolate. They will +// block Dart execution. This will cause dropped frames in Flutter applications. +// Instead, call these native functions on a separate isolate. +FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b); diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..b3eb2be1 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..a5cd956d --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,28 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "rust_in_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +# Invoke the build for native code shared with the other target platforms. +# This can be changed to accommodate different builds. +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(rust_in_flutter_bundled_libraries + # Defined in ../src/CMakeLists.txt. + # This can be changed to accommodate different builds. + $ + PARENT_SCOPE +) + +include(./gen_msg.cmake) + +# Include the CMake build file of the Rust code. +include(./rust.cmake) \ No newline at end of file diff --git a/windows/gen_msg.cmake b/windows/gen_msg.cmake new file mode 100644 index 00000000..6736ebee --- /dev/null +++ b/windows/gen_msg.cmake @@ -0,0 +1,12 @@ +execute_process( + COMMAND cmd /c dart run rust_in_flutter message + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/../ + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error_output +) +if(result EQUAL 0) + message("Generate protobuf messages successfully:${output}") +else() + message(FATAL_ERROR "Generate protobuf messages failed with error code ${result}. Error output:${error_output}") +endif() \ No newline at end of file diff --git a/windows/rust.cmake b/windows/rust.cmake new file mode 100644 index 00000000..b192ac7a --- /dev/null +++ b/windows/rust.cmake @@ -0,0 +1,20 @@ +cmake_policy(SET CMP0079 NEW) +apply_standard_settings(${BINARY_NAME}) + +set_target_properties( + ${BINARY_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden + BUILD_RPATH_USE_ORIGIN ON +) + +target_compile_definitions(${BINARY_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${BINARY_NAME} ${CMAKE_SOURCE_DIR}/../native/hub hub "") + +set( + rust_in_flutter_bundled_libraries + "${${BINARY_NAME}_cargokit_lib}" + PARENT_SCOPE +) \ No newline at end of file