diff --git a/Cargo.toml b/Cargo.toml index 61bdb9c..0b39116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] +members = ["example/dart_package/rust", "example/flutter_package/rust"] resolver = "2" diff --git a/example/dart_package/.gitignore b/example/dart_package/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/example/dart_package/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/example/dart_package/CHANGELOG.md b/example/dart_package/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/example/dart_package/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/example/dart_package/LICENSE b/example/dart_package/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/example/dart_package/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/example/dart_package/README.md b/example/dart_package/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/example/dart_package/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/example/dart_package/analysis_options.yaml b/example/dart_package/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/example/dart_package/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/example/dart_package/example/dart_package_example.dart b/example/dart_package/example/dart_package_example.dart new file mode 100644 index 0000000..7af3677 --- /dev/null +++ b/example/dart_package/example/dart_package_example.dart @@ -0,0 +1,6 @@ +import 'package:dart_package/dart_package.dart'; + +void main() { + final value = sum(10, 15); + print('sum(10, 15) = $value'); +} diff --git a/example/dart_package/hook/build.dart b/example/dart_package/hook/build.dart new file mode 100644 index 0000000..64a786c --- /dev/null +++ b/example/dart_package/hook/build.dart @@ -0,0 +1,20 @@ +import 'dart:io'; + +import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + try { + await build(args, (BuildConfig buildConfig, BuildOutput output) async { + final builder = RustBuilder( + package: 'dart_package', + crateManifestPath: 'rust/Cargo.toml', + buildConfig: buildConfig, + ); + await builder.run(output: output); + }); + } catch (e) { + print(e); + exit(1); + } +} diff --git a/example/dart_package/lib/dart_package.dart b/example/dart_package/lib/dart_package.dart new file mode 100644 index 0000000..e8189bc --- /dev/null +++ b/example/dart_package/lib/dart_package.dart @@ -0,0 +1,8 @@ +/// Support for doing something awesome. +/// +/// More dartdocs go here. +library; + +export 'src/dart_package_base.dart'; + +// TODO: Export any libraries intended for clients of this package. diff --git a/example/dart_package/lib/src/dart_package_base.dart b/example/dart_package/lib/src/dart_package_base.dart new file mode 100644 index 0000000..8811c79 --- /dev/null +++ b/example/dart_package/lib/src/dart_package_base.dart @@ -0,0 +1,3 @@ +import 'ffi_rust.dart' as ffi_rust; + +int sum(int a, int b) => ffi_rust.sum(a, b); diff --git a/example/dart_package/lib/src/ffi_rust.dart b/example/dart_package/lib/src/ffi_rust.dart new file mode 100644 index 0000000..cbf3177 --- /dev/null +++ b/example/dart_package/lib/src/ffi_rust.dart @@ -0,0 +1,10 @@ +@ffi.DefaultAsset('package:dart_package/dart_ffi_plugin') +library rust; + +import 'dart:ffi' as ffi; + +@ffi.Native() +external int sum( + int a, + int b, +); diff --git a/example/dart_package/native_manifest.yaml b/example/dart_package/native_manifest.yaml new file mode 100644 index 0000000..bc6e545 --- /dev/null +++ b/example/dart_package/native_manifest.yaml @@ -0,0 +1,5 @@ +version: 0.1.0 +requirements: + rust: + stable: + version: 1.77.2 diff --git a/example/dart_package/pubspec.yaml b/example/dart_package/pubspec.yaml new file mode 100644 index 0000000..7c1c887 --- /dev/null +++ b/example/dart_package/pubspec.yaml @@ -0,0 +1,15 @@ +name: dart_package +description: A starting point for Dart libraries or applications. +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.5.0-90.0.dev + +dependencies: + native_toolchain_rust: ^0.1.0-dev.2 + native_assets_cli: ^0.5.4 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/example/dart_package/rust/Cargo.lock b/example/dart_package/rust/Cargo.lock new file mode 100644 index 0000000..17772bb --- /dev/null +++ b/example/dart_package/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hello_ffi_plugin" +version = "0.1.0" diff --git a/example/dart_package/rust/Cargo.toml b/example/dart_package/rust/Cargo.toml new file mode 100644 index 0000000..f1a8353 --- /dev/null +++ b/example/dart_package/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "dart_ffi_plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/example/dart_package/rust/src/lib.rs b/example/dart_package/rust/src/lib.rs new file mode 100644 index 0000000..230bf35 --- /dev/null +++ b/example/dart_package/rust/src/lib.rs @@ -0,0 +1,18 @@ +use std::usize; + +#[no_mangle] +pub extern "C" fn sum(a: usize, b: usize) -> usize { + println!("Hello from rust {a} + {b}"); + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = sum(2, 2); + assert_eq!(result, 4); + } +} diff --git a/example/dart_package/test/dart_package_test.dart b/example/dart_package/test/dart_package_test.dart new file mode 100644 index 0000000..8c0b9fa --- /dev/null +++ b/example/dart_package/test/dart_package_test.dart @@ -0,0 +1,12 @@ +import 'package:dart_package/dart_package.dart'; + +import 'package:test/test.dart'; + +void main() { + group('A group of tests', () { + test('FFI call works', () { + final result = sum(10, 15); + expect(result, 25); + }); + }); +} diff --git a/example/flutter_package/.gitignore b/example/flutter_package/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/example/flutter_package/.gitignore @@ -0,0 +1,29 @@ +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/example/flutter_package/.metadata b/example/flutter_package/.metadata new file mode 100644 index 0000000..c0f8133 --- /dev/null +++ b/example/flutter_package/.metadata @@ -0,0 +1,10 @@ +# 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 and should not be manually edited. + +version: + revision: "9689f7f89c9821b344321668fd144bdd6b957d41" + channel: "main" + +project_type: package diff --git a/example/flutter_package/CHANGELOG.md b/example/flutter_package/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/example/flutter_package/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/example/flutter_package/LICENSE b/example/flutter_package/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/example/flutter_package/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/example/flutter_package/README.md b/example/flutter_package/README.md new file mode 100644 index 0000000..02fe8ec --- /dev/null +++ b/example/flutter_package/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/example/flutter_package/analysis_options.yaml b/example/flutter_package/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/example/flutter_package/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/flutter_package/example/.gitignore b/example/flutter_package/example/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/example/flutter_package/example/.gitignore @@ -0,0 +1,43 @@ +# 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 +.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 diff --git a/example/flutter_package/example/.metadata b/example/flutter_package/example/.metadata new file mode 100644 index 0000000..59e4e2d --- /dev/null +++ b/example/flutter_package/example/.metadata @@ -0,0 +1,45 @@ +# 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 and should not be manually edited. + +version: + revision: "9689f7f89c9821b344321668fd144bdd6b957d41" + channel: "main" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: android + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: ios + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: linux + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: macos + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: web + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + - platform: windows + create_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + base_revision: 9689f7f89c9821b344321668fd144bdd6b957d41 + + # 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/example/flutter_package/example/README.md b/example/flutter_package/example/README.md new file mode 100644 index 0000000..1b7a4e3 --- /dev/null +++ b/example/flutter_package/example/README.md @@ -0,0 +1,3 @@ +# example + +A new Flutter project. diff --git a/example/flutter_package/example/analysis_options.yaml b/example/flutter_package/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/example/flutter_package/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/example/flutter_package/example/android/.gitignore b/example/flutter_package/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/android/app/build.gradle b/example/flutter_package/example/android/app/build.gradle new file mode 100644 index 0000000..2f5eea4 --- /dev/null +++ b/example/flutter_package/example/android/app/build.gradle @@ -0,0 +1,40 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // 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. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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 = "../.." +} diff --git a/example/flutter_package/example/android/app/src/debug/AndroidManifest.xml b/example/flutter_package/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/flutter_package/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/flutter_package/example/android/app/src/main/AndroidManifest.xml b/example/flutter_package/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..74a78b9 --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/flutter_package/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..70f8f08 --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/example/flutter_package/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/flutter_package/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/flutter_package/example/android/app/src/main/res/drawable/launch_background.xml b/example/flutter_package/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/flutter_package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/flutter_package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/example/flutter_package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/flutter_package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/flutter_package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/example/flutter_package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/flutter_package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/flutter_package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/example/flutter_package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/flutter_package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/flutter_package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/example/flutter_package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/flutter_package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/flutter_package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/example/flutter_package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/flutter_package/example/android/app/src/main/res/values-night/styles.xml b/example/flutter_package/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/flutter_package/example/android/app/src/main/res/values/styles.xml b/example/flutter_package/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/flutter_package/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/flutter_package/example/android/app/src/profile/AndroidManifest.xml b/example/flutter_package/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/flutter_package/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/flutter_package/example/android/build.gradle b/example/flutter_package/example/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/example/flutter_package/example/android/build.gradle @@ -0,0 +1,18 @@ +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/flutter_package/example/android/gradle.properties b/example/flutter_package/example/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/example/flutter_package/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/flutter_package/example/android/gradle/wrapper/gradle-wrapper.properties b/example/flutter_package/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/example/flutter_package/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.6.3-all.zip diff --git a/example/flutter_package/example/android/settings.gradle b/example/flutter_package/example/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/example/flutter_package/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/example/flutter_package/example/ios/.gitignore b/example/flutter_package/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/ios/Flutter/AppFrameworkInfo.plist b/example/flutter_package/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/example/flutter_package/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 + 12.0 + + diff --git a/example/flutter_package/example/ios/Flutter/Debug.xcconfig b/example/flutter_package/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/flutter_package/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/flutter_package/example/ios/Flutter/Release.xcconfig b/example/flutter_package/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/flutter_package/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/flutter_package/example/ios/Runner.xcodeproj/project.pbxproj b/example/flutter_package/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..037d56e --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*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 */; }; + 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 */; }; +/* 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + 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 */, + ); + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + 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 = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + 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 = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + 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 */ + 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"; + }; + 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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 = DL9X48QE4H; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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; + 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.example.example.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; + 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.example.example.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; + 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.example.example.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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 = DL9X48QE4H; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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 = DL9X48QE4H; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/flutter_package/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/flutter_package/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/flutter_package/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/flutter_package/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/flutter_package/example/ios/Runner/AppDelegate.swift b/example/flutter_package/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/example/flutter_package/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@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/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/flutter_package/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/example/flutter_package/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. \ No newline at end of file diff --git a/example/flutter_package/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/flutter_package/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/flutter_package/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/ios/Runner/Base.lproj/Main.storyboard b/example/flutter_package/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/example/flutter_package/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/ios/Runner/Info.plist b/example/flutter_package/example/ios/Runner/Info.plist new file mode 100644 index 0000000..5458fc4 --- /dev/null +++ b/example/flutter_package/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + 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 + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/flutter_package/example/ios/Runner/Runner-Bridging-Header.h b/example/flutter_package/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/flutter_package/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/flutter_package/example/ios/RunnerTests/RunnerTests.swift b/example/flutter_package/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/lib/main.dart b/example/flutter_package/example/lib/main.dart new file mode 100644 index 0000000..42ab127 --- /dev/null +++ b/example/flutter_package/example/lib/main.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_package/flutter_package.dart'; + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Text('Sum value: ${sum(10, 15)}'), + ), + ), + ); + } +} diff --git a/example/flutter_package/example/linux/.gitignore b/example/flutter_package/example/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/example/flutter_package/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/flutter_package/example/linux/CMakeLists.txt b/example/flutter_package/example/linux/CMakeLists.txt new file mode 100644 index 0000000..9cb0d1d --- /dev/null +++ b/example/flutter_package/example/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# 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") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# 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) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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/flutter_package/example/linux/flutter/CMakeLists.txt b/example/flutter_package/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/linux/flutter/generated_plugin_registrant.cc b/example/flutter_package/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/linux/flutter/generated_plugin_registrant.h b/example/flutter_package/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/linux/flutter/generated_plugins.cmake b/example/flutter_package/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/example/flutter_package/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +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/flutter_package/example/linux/main.cc b/example/flutter_package/example/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/linux/my_application.cc b/example/flutter_package/example/linux/my_application.cc new file mode 100644 index 0000000..c0530d4 --- /dev/null +++ b/example/flutter_package/example/linux/my_application.cc @@ -0,0 +1,124 @@ +#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"); + 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"); + } + + 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 GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// 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_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + 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/flutter_package/example/linux/my_application.h b/example/flutter_package/example/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/.gitignore b/example/flutter_package/example/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/example/flutter_package/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/example/flutter_package/example/macos/Flutter/Flutter-Debug.xcconfig b/example/flutter_package/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/example/flutter_package/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/flutter_package/example/macos/Flutter/Flutter-Release.xcconfig b/example/flutter_package/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/example/flutter_package/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/flutter_package/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/flutter_package/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner.xcodeproj/project.pbxproj b/example/flutter_package/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..daa7bf1 --- /dev/null +++ b/example/flutter_package/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*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 */; }; +/* 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 */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 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 */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.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 = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 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 = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + 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"; + }; +/* 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; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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/flutter_package/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter_package/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/flutter_package/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/flutter_package/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/flutter_package/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..15368ec --- /dev/null +++ b/example/flutter_package/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/flutter_package/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/flutter_package/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/flutter_package/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/flutter_package/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/flutter_package/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/flutter_package/example/macos/Runner/AppDelegate.swift b/example/flutter_package/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..8e02df2 --- /dev/null +++ b/example/flutter_package/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/example/flutter_package/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/flutter_package/example/macos/Runner/Base.lproj/MainMenu.xib b/example/flutter_package/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/example/flutter_package/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/flutter_package/example/macos/Runner/Configs/AppInfo.xcconfig b/example/flutter_package/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..92fb3cd --- /dev/null +++ b/example/flutter_package/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 + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/example/flutter_package/example/macos/Runner/Configs/Debug.xcconfig b/example/flutter_package/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/example/flutter_package/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/flutter_package/example/macos/Runner/Configs/Release.xcconfig b/example/flutter_package/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/example/flutter_package/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/flutter_package/example/macos/Runner/Configs/Warnings.xcconfig b/example/flutter_package/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner/DebugProfile.entitlements b/example/flutter_package/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner/Info.plist b/example/flutter_package/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner/MainFlutterWindow.swift b/example/flutter_package/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/macos/Runner/Release.entitlements b/example/flutter_package/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/example/flutter_package/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/example/flutter_package/example/macos/RunnerTests/RunnerTests.swift b/example/flutter_package/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/example/flutter_package/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +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/flutter_package/example/pubspec.lock b/example/flutter_package/example/pubspec.lock new file mode 100644 index 0000000..be61f07 --- /dev/null +++ b/example/flutter_package/example/pubspec.lock @@ -0,0 +1,553 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + 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" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + url: "https://pub.dev" + source: hosted + version: "1.7.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_package: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + 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: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b" + url: "https://pub.dev" + source: hosted + version: "1.14.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + native_assets_cli: + dependency: transitive + description: + name: native_assets_cli + sha256: "9c1b67ccf85ec9282f34e5348ae78dcb7da2c7dc965c0265306477d977853a0d" + url: "https://pub.dev" + source: hosted + version: "0.5.4" + native_toolchain_rust: + dependency: "direct overridden" + description: + path: "../../../native_toolchain_rust" + relative: true + source: path + version: "0.1.0-dev.2" + native_toolchain_rust_common: + dependency: "direct overridden" + description: + path: "../../../native_toolchain_rust_common" + relative: true + source: path + version: "0.1.0-dev.2" + 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: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + rustup: + dependency: "direct overridden" + description: + path: "../../../rustup" + relative: true + source: path + version: "0.1.0-dev.2" + 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" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + 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: transitive + 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: transitive + description: + name: test + sha256: d72b538180efcf8413cd2e4e6fcc7ae99c7712e0909eb9223f9da6e6d0ef715f + url: "https://pub.dev" + source: hosted + version: "1.25.4" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4d070a6bc36c1c4e89f20d353bfd71dc30cdf2bd0e14349090af360a029ab292" + url: "https://pub.dev" + source: hosted + version: "0.6.2" + toml: + dependency: transitive + description: + name: toml + sha256: "9968de24e45b632bf1a654fe1ac7b6fe5261c349243df83fd262397799c45a2d" + url: "https://pub.dev" + source: hosted + version: "0.15.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.0-90.0.dev <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/flutter_package/example/pubspec.yaml b/example/flutter_package/example/pubspec.yaml new file mode 100644 index 0000000..f83902b --- /dev/null +++ b/example/flutter_package/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: example +description: "A new Flutter project." +publish_to: 'none' +version: 0.1.0 + +environment: + sdk: '>=3.5.0-90.0.dev <4.0.0' + +dependencies: + flutter: + sdk: flutter + flutter_package: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/example/flutter_package/example/test/example_test.dart b/example/flutter_package/example/test/example_test.dart new file mode 100644 index 0000000..8cc1f9b --- /dev/null +++ b/example/flutter_package/example/test/example_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_package/flutter_package.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('sum test', () { + expect(sum(10, 20), 30); + }); +} diff --git a/example/flutter_package/example/web/favicon.png b/example/flutter_package/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/example/flutter_package/example/web/favicon.png differ diff --git a/example/flutter_package/example/web/icons/Icon-192.png b/example/flutter_package/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/example/flutter_package/example/web/icons/Icon-192.png differ diff --git a/example/flutter_package/example/web/icons/Icon-512.png b/example/flutter_package/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/example/flutter_package/example/web/icons/Icon-512.png differ diff --git a/example/flutter_package/example/web/icons/Icon-maskable-192.png b/example/flutter_package/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/example/flutter_package/example/web/icons/Icon-maskable-192.png differ diff --git a/example/flutter_package/example/web/icons/Icon-maskable-512.png b/example/flutter_package/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/example/flutter_package/example/web/icons/Icon-maskable-512.png differ diff --git a/example/flutter_package/example/web/index.html b/example/flutter_package/example/web/index.html new file mode 100644 index 0000000..1aa025d --- /dev/null +++ b/example/flutter_package/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + diff --git a/example/flutter_package/example/web/manifest.json b/example/flutter_package/example/web/manifest.json new file mode 100644 index 0000000..096edf8 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/.gitignore b/example/flutter_package/example/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/CMakeLists.txt b/example/flutter_package/example/windows/CMakeLists.txt new file mode 100644 index 0000000..d960948 --- /dev/null +++ b/example/flutter_package/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example 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") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# 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() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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/flutter_package/example/windows/flutter/CMakeLists.txt b/example/flutter_package/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/example/flutter_package/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# 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") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === 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" + ${FLUTTER_TARGET_PLATFORM} $ + 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/flutter_package/example/windows/flutter/generated_plugin_registrant.cc b/example/flutter_package/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/flutter/generated_plugin_registrant.h b/example/flutter_package/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/flutter/generated_plugins.cmake b/example/flutter_package/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/example/flutter_package/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +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/flutter_package/example/windows/runner/CMakeLists.txt b/example/flutter_package/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/Runner.rc b/example/flutter_package/example/windows/runner/Runner.rc new file mode 100644 index 0000000..687e6bd --- /dev/null +++ b/example/flutter_package/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.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\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/flutter_package/example/windows/runner/flutter_window.cpp b/example/flutter_package/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/flutter_window.h b/example/flutter_package/example/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/main.cpp b/example/flutter_package/example/windows/runner/main.cpp new file mode 100644 index 0000000..a61bf80 --- /dev/null +++ b/example/flutter_package/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", 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/flutter_package/example/windows/runner/resource.h b/example/flutter_package/example/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/resources/app_icon.ico b/example/flutter_package/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/example/flutter_package/example/windows/runner/resources/app_icon.ico differ diff --git a/example/flutter_package/example/windows/runner/runner.exe.manifest b/example/flutter_package/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/example/flutter_package/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/example/flutter_package/example/windows/runner/utils.cpp b/example/flutter_package/example/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/example/flutter_package/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(); + } + unsigned 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/flutter_package/example/windows/runner/utils.h b/example/flutter_package/example/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/win32_window.cpp b/example/flutter_package/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/example/flutter_package/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/flutter_package/example/windows/runner/win32_window.h b/example/flutter_package/example/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/example/flutter_package/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/example/flutter_package/hook/build.dart b/example/flutter_package/hook/build.dart new file mode 100644 index 0000000..053a8f4 --- /dev/null +++ b/example/flutter_package/hook/build.dart @@ -0,0 +1,23 @@ +import 'dart:io'; + +import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + try { + await build(args, (BuildConfig buildConfig, BuildOutput output) async { + final builder = RustBuilder( + // The ID of native assets consists of package name and crate name. + package: 'flutter_package', + crateManifestPath: 'rust/Cargo.toml', + buildConfig: buildConfig, + ); + await builder.run(output: output); + }); + } catch (e) { + // FIXME(knopp): Figure out where to log the error + // https://github.com/flutter/flutter/issues/147544 + stdout.writeln(e); + exit(1); + } +} diff --git a/example/flutter_package/lib/flutter_package.dart b/example/flutter_package/lib/flutter_package.dart new file mode 100644 index 0000000..676ccec --- /dev/null +++ b/example/flutter_package/lib/flutter_package.dart @@ -0,0 +1,3 @@ +library flutter_package; + +export 'src/flutter_package.dart'; diff --git a/example/flutter_package/lib/src/ffi_rust.dart b/example/flutter_package/lib/src/ffi_rust.dart new file mode 100644 index 0000000..da5c6a1 --- /dev/null +++ b/example/flutter_package/lib/src/ffi_rust.dart @@ -0,0 +1,10 @@ +@ffi.DefaultAsset('package:flutter_package/flutter_ffi_plugin') +library rust; + +import 'dart:ffi' as ffi; + +@ffi.Native() +external int sum( + int a, + int b, +); diff --git a/example/flutter_package/lib/src/flutter_package.dart b/example/flutter_package/lib/src/flutter_package.dart new file mode 100644 index 0000000..8811c79 --- /dev/null +++ b/example/flutter_package/lib/src/flutter_package.dart @@ -0,0 +1,3 @@ +import 'ffi_rust.dart' as ffi_rust; + +int sum(int a, int b) => ffi_rust.sum(a, b); diff --git a/example/flutter_package/native_manifest.yaml b/example/flutter_package/native_manifest.yaml new file mode 100644 index 0000000..43cbc11 --- /dev/null +++ b/example/flutter_package/native_manifest.yaml @@ -0,0 +1,7 @@ +version: 0.1.0 +requirements: + ndk: + version: 25.1.8937393 + rust: + stable: + version: 1.77.2 diff --git a/example/flutter_package/pubspec.yaml b/example/flutter_package/pubspec.yaml new file mode 100644 index 0000000..51a926d --- /dev/null +++ b/example/flutter_package/pubspec.yaml @@ -0,0 +1,56 @@ +name: flutter_package +description: "A new Flutter package project." +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.5.0-90.0.dev <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + native_toolchain_rust: ^0.1.0-dev.2 + native_assets_cli: ^0.5.4 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.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: + + # To add assets to your 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 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/example/flutter_package/rust/Cargo.lock b/example/flutter_package/rust/Cargo.lock new file mode 100644 index 0000000..17772bb --- /dev/null +++ b/example/flutter_package/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hello_ffi_plugin" +version = "0.1.0" diff --git a/example/flutter_package/rust/Cargo.toml b/example/flutter_package/rust/Cargo.toml new file mode 100644 index 0000000..655ef68 --- /dev/null +++ b/example/flutter_package/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "flutter_ffi_plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/example/flutter_package/rust/src/lib.rs b/example/flutter_package/rust/src/lib.rs new file mode 100644 index 0000000..230bf35 --- /dev/null +++ b/example/flutter_package/rust/src/lib.rs @@ -0,0 +1,18 @@ +use std::usize; + +#[no_mangle] +pub extern "C" fn sum(a: usize, b: usize) -> usize { + println!("Hello from rust {a} + {b}"); + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = sum(2, 2); + assert_eq!(result, 4); + } +} diff --git a/example/flutter_package/test/flutter_package_test.dart b/example/flutter_package/test/flutter_package_test.dart new file mode 100644 index 0000000..db04e68 --- /dev/null +++ b/example/flutter_package/test/flutter_package_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_package/flutter_package.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('A group of tests', () { + test('FFI call works', () { + final result = sum(10, 15); + expect(result, 25); + }); + }); +} diff --git a/melos.yaml b/melos.yaml index 7a6e0c4..be33755 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,3 +1,11 @@ name: native_toolchain_rust packages: + - native_toolchain_rust + - native_toolchain_rust_common + - rustup + - native_doctor + - example/dart_package + - example/flutter_package + - example/flutter_package/example + diff --git a/native_doctor/.gitignore b/native_doctor/.gitignore new file mode 100644 index 0000000..3a85790 --- /dev/null +++ b/native_doctor/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/native_doctor/CHANGELOG.md b/native_doctor/CHANGELOG.md new file mode 100644 index 0000000..58f846d --- /dev/null +++ b/native_doctor/CHANGELOG.md @@ -0,0 +1,12 @@ +## 0.1.0-dev.3 + + - **FIX**: correct rustup dependency version. + - **FEAT**: add flutter package example. + - **FEAT**: add dart example + refactor. + - **FEAT**: native_doctor tweaks. + - **FEAT**: add native_doctor. + - **DOCS**: improve documentation (#2). + +## 0.1.0-dev.1 + +- Initial version. diff --git a/native_doctor/LICENSE b/native_doctor/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/native_doctor/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/native_doctor/README.md b/native_doctor/README.md new file mode 100644 index 0000000..1eda5d6 --- /dev/null +++ b/native_doctor/README.md @@ -0,0 +1,93 @@ +# Native Doctor + +`native_doctor` is a tool analyzes Flutter and Dart project dependencies, find requirements for native toolchains and, if possible, resolves issues. + +Currently supported native toolchains: +- NDK +- Rust (through rustup) + +## Usage + +In your project directory, run: +```bash +dart pub global activate native_doctor +dart pub global run native_doctor +``` + +## Features + +`native_doctor` can + +- Check for installed NDKs and install NDK if missing or outdated. +- Checks whether Rust is installed and install Rust (through Rustup) if missing. +- Check for installed Rust toolchains and targets and install missing toolchains and targets. + +## Supporting `native_doctor` in Flutter or Dart packages + +If your package depends on NDK or Rust being present during compilation, add a +`native_manifest.yaml` file to the root of the package. This file contains minimal required versions of NDK and/or Rust toolchains. + +Example `native_manifest.yaml` file: + +```yaml +version: 0.1.0 +requirements: + ndk: + version: 26.0.0 + rust: + stable: + version: 1.77.2 +``` + +Example `native_manifest.yaml` file if project only requires NDK: + +```yaml +version: 0.1.0 +requirements: + ndk: + version: 26.0.0 +``` + +Example `native_manifest.yaml` file if project only requires both stable and nightly Rust and NDK: + +```yaml +version: 0.1.0 +requirements: + ndk: + version: 26.0.0 + rust: + stable: + version: 1.77.2 + nightly: + version: 1.79.0-nightly +``` + +Example output of running `native_doctor` in a project with native dependencies: +``` +Project: native_toolchain_rust_test (Flutter) +Buildable platforms: macos, ios, android + +Native toolchain: NDK + + [✗] NDK installed, but too old + ! Installed versions: 25.1.8937393, 23.1.7779620 + ! Required minimum version: 26.0.0 + +Native toolchain: Rust + + [✓] Rustup installed + [✗] Toolchain stable-aarch64-apple-darwin (version 1.77.2) + • Required minimum version: 1.77.2 + • Installed targets: aarch64-apple-darwin, aarch64-apple-ios, + aarch64-apple-ios-sim, aarch64-linux-android, arm-linux-androideabi, + x86_64-linux-android + ! Missing targets: i686-linux-android, x86_64-apple-ios, x86_64-apple-darwin + +Proposed actions: + + • (NDK) Install NDK 26.0.0 or newer + • (Rust) Install targets i686-linux-android, x86_64-apple-ios, x86_64-apple-darwin + for toolchain stable-aarch64-apple-darwin + +Do you want native doctor to perform proposed actions? (y/N) +``` diff --git a/native_doctor/analysis_options.yaml b/native_doctor/analysis_options.yaml new file mode 100644 index 0000000..12e713a --- /dev/null +++ b/native_doctor/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:lints/recommended.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/native_doctor/bin/native_doctor.dart b/native_doctor/bin/native_doctor.dart new file mode 100644 index 0000000..d9ddbbf --- /dev/null +++ b/native_doctor/bin/native_doctor.dart @@ -0,0 +1,10 @@ +import 'package:logging/logging.dart'; +import 'package:native_doctor/native_doctor.dart'; + +void main(List arguments) async { + hierarchicalLoggingEnabled = true; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + run(arguments); +} diff --git a/native_doctor/lib/native_doctor.dart b/native_doctor/lib/native_doctor.dart new file mode 100644 index 0000000..78c84b3 --- /dev/null +++ b/native_doctor/lib/native_doctor.dart @@ -0,0 +1 @@ +export 'src/native_doctor.dart'; diff --git a/native_doctor/lib/src/android_sdk.dart b/native_doctor/lib/src/android_sdk.dart new file mode 100644 index 0000000..90da58e --- /dev/null +++ b/native_doctor/lib/src/android_sdk.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:logging/logging.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:path/path.dart' as path; + +class AndroidSdkInfo { + AndroidSdkInfo({ + required this.androidSdk, + required this.javaHome, + }); + + final String androidSdk; + final String javaHome; + + static Future find({ + String? flutterRoot, + Logger? logger, + }) async { + final flutterCommand = flutterRoot != null + ? path.join(flutterRoot, 'bin', 'flutter') + : 'flutter'; + final result = await runCommand( + flutterCommand, + [ + 'config', + '--machine', + ], + logger: logger, + ); + final json = result.stdout as String; + final decoded = jsonDecode(json) as Map; + final javaHome = decoded['jdk-dir'] as String?; + final sdk = decoded['android-sdk'] as String?; + if (javaHome == null || sdk == null) { + return null; + } + return AndroidSdkInfo( + androidSdk: sdk, + javaHome: javaHome, + ); + } +} diff --git a/native_doctor/lib/src/checkers/ndk.dart b/native_doctor/lib/src/checkers/ndk.dart new file mode 100644 index 0000000..84dd3d3 --- /dev/null +++ b/native_doctor/lib/src/checkers/ndk.dart @@ -0,0 +1,237 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_doctor/src/android_sdk.dart'; +import 'package:native_doctor/src/native_doctor.dart'; +import 'package:native_doctor/src/toolchain_checker.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +class _Ndk { + _Ndk({ + required this.version, + required this.uri, + }); + + final Version version; + final Uri uri; +} + +class NdkToolchainChecker extends ToolchainChecker { + NdkToolchainChecker({ + required this.doctor, + }); + + final NativeDoctor doctor; + + @override + String get displayName => 'NDK'; + + @override + String get manifestKey => 'ndk'; + + Version? minimumVersion; + + @override + @override + Future updateFromManifest( + ManifestContext context, + YamlNode manifestNode, + ) async { + final manifestInfo = NdkManifestInfo.parse(manifestNode); + if (minimumVersion == null || minimumVersion! < manifestInfo.version) { + minimumVersion = manifestInfo.version; + } + } + + AndroidSdkInfo? _sdkInfo; + + Future> _findInstalledNdks() async { + final sdkRoot = _sdkInfo?.androidSdk; + if (sdkRoot == null) { + return []; + } + + final ndkRoot = Uri.directory(sdkRoot).resolve('ndk'); + final ndkDir = Directory.fromUri(ndkRoot); + if (!await ndkDir.exists()) { + return []; + } + + final result = <_Ndk>[]; + + final ndkDirs = await ndkDir.list().toList(); + for (final dir in ndkDirs) { + final sourceProperties = + File.fromUri(dir.uri.resolve('source.properties')); + if (await sourceProperties.exists()) { + final content = await sourceProperties.readAsString(); + final revision = content + .split('\n') + .firstWhere((line) => line.startsWith('Pkg.Revision')) + .split('=') + .last + .trim(); + final version = Version.parse(revision); + result.add(_Ndk( + version: version, + uri: dir.uri, + )); + } + } + return result; + } + + bool needNewerVersion = false; + + @override + Future validate() async { + // No package needs NDK + if (minimumVersion == null || + !doctor.targetPlatforms.contains(OS.android)) { + return null; + } + + _sdkInfo = await AndroidSdkInfo.find( + flutterRoot: doctor.flutterRoot?.toFilePath(), + logger: doctor.verboseLogger, + ); + + if (_sdkInfo == null) { + return ValidationResult(sections: [ + ValidationResultSection( + ok: false, + title: 'Android SDK not found', + messages: [], + ), + ], proposedActions: []); + } + + final ndks = await _findInstalledNdks(); + if (ndks.isEmpty) { + return ValidationResult( + sections: [ + ValidationResultSection( + ok: false, + title: 'NDK not found', + messages: [], + ), + ], + proposedActions: [ + ProposedAction( + description: 'Install NDK', + ), + ], + ); + } else { + needNewerVersion = ndks.none((ndk) => ndk.version >= minimumVersion!); + return ValidationResult( + sections: [ + ValidationResultSection( + ok: !needNewerVersion, + title: needNewerVersion + ? 'NDK installed, but too old' + : 'NDK installed', + messages: [ + ValidationResultSectionMessage( + ok: !needNewerVersion, + message: + 'Installed versions: ${ndks.map((e) => e.version).join(', ')}', + ), + ValidationResultSectionMessage( + ok: !needNewerVersion, + message: 'Required minimum version: $minimumVersion', + ), + ], + ), + ], + proposedActions: [ + if (needNewerVersion) + ProposedAction( + description: 'Install NDK $minimumVersion or newer', + ), + ], + ); + } + } + + static Version? findBestNdkKversion({ + required String sdkManagerOutput, + required Version minimumVersion, + }) { + Version? bestVersion; + final lines = sdkManagerOutput.split('\n'); + for (var line in lines) { + if (line.trim().startsWith('ndk;')) { + final columns = line.split('|'); + if (columns.length < 3) { + continue; + } + // Pre-release versions have another string in the second column, i.e. + // 27.0.11718014 rc1 + final isPrerelease = columns[1].trim().split(' ').length > 1; + final version = columns[0].split(';').last.trim(); + final parsedVersion = Version.parse(version); + if (parsedVersion >= minimumVersion && + (bestVersion == null || parsedVersion > bestVersion)) { + // Use prerelease only if there is no stable version. + if (isPrerelease && bestVersion != null) { + continue; + } + if (minimumVersion < parsedVersion) { + bestVersion = parsedVersion; + } + } + } + } + + return bestVersion; + } + + @override + Future fix(ActionLogger logger) async { + if (!needNewerVersion) { + return; + } + final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; + final sdkManager = path.join( + _sdkInfo!.androidSdk, + 'cmdline-tools', + 'latest', + 'bin', + 'sdkmanager$sdkManagerExtension', + ); + + Version? latestVersion; + + await logger.logAction('Fetching NDK list...', () async { + final list = await runCommand( + sdkManager, + ['--list'], + logger: doctor.verboseLogger, + ); + latestVersion = findBestNdkKversion( + sdkManagerOutput: list.stdout, + minimumVersion: minimumVersion!, + ); + if (latestVersion == null) { + throw Exception('Failed to find latest NDK version'); + } + }); + if (latestVersion! < minimumVersion!) { + doctor.writer.printMessage( + 'No NDK version available that meets the minimum requirement.'); + return; + } + await logger.logAction('Installing NDK $latestVersion', () async { + await runCommand( + sdkManager, + ['--install', 'ndk;$latestVersion'], + logger: doctor.verboseLogger, + ); + }); + } +} diff --git a/native_doctor/lib/src/checkers/rustup.dart b/native_doctor/lib/src/checkers/rustup.dart new file mode 100644 index 0000000..cc64519 --- /dev/null +++ b/native_doctor/lib/src/checkers/rustup.dart @@ -0,0 +1,216 @@ +import 'package:collection/collection.dart'; +import 'package:native_doctor/src/tool_error.dart'; +import 'package:native_doctor/src/writer.dart'; +import 'package:native_doctor/src/toolchain_checker.dart'; +import 'package:native_doctor/src/native_doctor.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:rustup/rustup.dart'; +import 'package:yaml/yaml.dart'; + +class _RustToolchainRequirement { + final String name; + final Version minimalVersion; + + _RustToolchainRequirement({ + required this.name, + required this.minimalVersion, + }); +} + +class _RustToolchainValidationResult { + _RustToolchainValidationResult({ + required this.name, + this.installedVersion, + this.requiredVersion, + this.installedTargets = const [], + this.missingTargets = const [], + }); + + bool get needInstall => installedVersion == null; + bool get needUpdate => + installedVersion != null && installedVersion! < requiredVersion!; + + final String name; + final Version? installedVersion; + final Version? requiredVersion; + final List installedTargets; + final List missingTargets; + + ValidationResultSection toValidationResultSection(TextStyler styler) { + return ValidationResultSection( + ok: !needInstall && !needUpdate && missingTargets.isEmpty, + title: needInstall + ? 'Toolchain ${styler.bolden(name)} not installed' + : 'Toolchain ${styler.bolden(name)} (version $installedVersion)', + messages: [ + ValidationResultSectionMessage( + ok: !needUpdate && !needInstall, + message: 'Required minimum version: $requiredVersion', + ), + if (installedTargets.isNotEmpty) + ValidationResultSectionMessage( + ok: true, + message: 'Installed targets: ${installedTargets.join(', ')}', + ), + if (missingTargets.isNotEmpty) + ValidationResultSectionMessage( + ok: false, + message: 'Missing targets: ${missingTargets.join(', ')}', + ), + ], + ); + } +} + +class RustToolchainChecker extends ToolchainChecker { + RustToolchainChecker({ + required this.doctor, + }); + + final NativeDoctor doctor; + + @override + String get manifestKey => 'rust'; + + @override + String get displayName => 'Rust'; + + final _requirements = <_RustToolchainRequirement>[]; + + @override + Future updateFromManifest( + ManifestContext context, + YamlNode manifestNode, + ) async { + final info = RustManifestInfo.parse(manifestNode); + for (final entry in info.toolchainToVersion.entries) { + final existing = + _requirements.firstWhereOrNull((r) => r.name == entry.key); + if (existing == null || existing.minimalVersion < entry.value) { + _requirements.removeWhere((r) => r.name == entry.key); + _requirements.add(_RustToolchainRequirement( + name: entry.key, + minimalVersion: entry.value, + )); + } + } + } + + List _requiredTargets() { + // For host-only (i.e. dart packages) environment don't + // require any targets. + if (doctor.hostOnly) { + return []; + } + final buildableSystems = doctor.targetPlatforms; + return RustTarget.allTargets.where((t) { + return buildableSystems.contains(t.os); + }).toList(growable: false); + } + + final results = <_RustToolchainValidationResult>[]; + + @override + Future validate() async { + if (_requirements.isEmpty) { + // No package requires rust. + return null; + } + final rustup = Rustup.systemRustup(logger: doctor.verboseLogger); + final requiredTargets = _requiredTargets(); + for (final requirement in _requirements) { + final toolchain = await rustup?.getToolchain(requirement.name); + final installedVersion = await toolchain?.rustVersion(); + final targets = await toolchain?.installedTargets() ?? []; + final missingTargets = requiredTargets.where((t) { + return !targets.contains(t); + }).toList(growable: false); + results.add( + _RustToolchainValidationResult( + name: toolchain?.name ?? requirement.name, + installedVersion: installedVersion, + requiredVersion: requirement.minimalVersion, + installedTargets: targets, + missingTargets: missingTargets, + ), + ); + } + + return ValidationResult(sections: [ + if (rustup == null) + ValidationResultSection( + ok: false, + title: 'Rustup not found', + messages: [], + ), + if (rustup != null) + ValidationResultSection( + ok: true, + title: 'Rustup installed', + messages: [], + ), + for (final result in results) + result.toValidationResultSection(doctor.writer), + ], proposedActions: [ + if (rustup == null) ProposedAction(description: 'Install rustup'), + if (results.any((r) => r.needUpdate)) + ProposedAction(description: 'Update Rust'), + for (final result in results) ...[ + if (result.needInstall) + ProposedAction(description: 'Install toolchain ${result.name}'), + if (result.missingTargets.isNotEmpty) + ProposedAction( + description: + 'Install target${result.missingTargets.length > 1 ? 's' : ''} ' + '${result.missingTargets.join(', ')} ' + 'for toolchain ${result.name}', + ), + ] + ]); + } + + @override + Future fix(ActionLogger logger) async { + var rustup = Rustup.systemRustup(logger: doctor.verboseLogger); + if (rustup == null) { + await logger.logAction('Installing rustup', () async { + final installer = await RustupInstaller.create(); + await installer.install(); + rustup = Rustup.systemRustup(logger: doctor.verboseLogger); + if (rustup == null) { + throw ToolError('Failed to install rustup'); + } + }); + } + + bool needUpdate = false; + for (final result in results) { + needUpdate |= result.needUpdate; + if (result.needInstall) { + await logger.logAction('Installing Rust toolchain ${result.name}', + () async { + await rustup!.installToolchain(result.name); + final toolchain = await rustup!.getToolchain(result.name); + if (toolchain == null) { + throw ToolError('Failed to install toolchain ${result.name}'); + } + }); + } + final toolchain = (await rustup!.getToolchain(result.name))!; + for (final target in result.missingTargets) { + await logger.logAction( + 'Installing target $target for toolchain ${result.name}', + () async { + await toolchain.installTarget(target); + }, + ); + } + } + if (needUpdate) { + await logger.logAction('Updating Rust', () async { + await rustup!.runCommand(['update']); + }); + } + } +} diff --git a/native_doctor/lib/src/native_doctor.dart b/native_doctor/lib/src/native_doctor.dart new file mode 100644 index 0000000..d8d467a --- /dev/null +++ b/native_doctor/lib/src/native_doctor.dart @@ -0,0 +1,320 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_doctor/src/checkers/ndk.dart'; +import 'package:native_doctor/src/writer.dart'; +import 'package:native_doctor/src/checkers/rustup.dart'; +import 'package:native_doctor/src/tool_error.dart'; +import 'package:native_doctor/src/toolchain_checker.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:package_config/package_config.dart'; +import 'package:pubspec_parse/pubspec_parse.dart'; + +class ToolOptions { + ToolOptions({ + required this.yes, + required this.verbose, + required this.path, + required this.writer, + }); + + final bool verbose; + final bool yes; + final Directory path; + final Writer writer; +} + +class NativeDoctor { + NativeDoctor({ + required this.options, + required this.packageConfig, + required this.pubspec, + required this.projectPlatforms, + this.flutterRoot, + }); + + final ToolOptions options; + final PackageConfig packageConfig; + final Pubspec pubspec; + final List projectPlatforms; + final Uri? flutterRoot; + + Writer get writer => options.writer; + final verboseLogger = Logger('verbose'); + + static Future withOptions(ToolOptions options) async { + final projectUri = Uri.directory(options.path.path); + + final pubspecUri = projectUri.resolve('pubspec.yaml'); + final pubspecFile = File.fromUri(pubspecUri); + if (!await pubspecFile.exists()) { + throw ToolError( + 'Could not find pubspec.yaml in "${projectUri.toFilePath()}".\n' + 'Path must be a Dart or Flutter project.'); + } + final pubspecContent = await pubspecFile.readAsString(); + final pubspec = Pubspec.parse(pubspecContent, sourceUrl: pubspecUri); + + final packageConfig = await findPackageConfig(options.path); + if (packageConfig == null) { + throw ToolError( + 'Could not find package config. Make sure to run `pub get` first.', + ); + } + Uri? flutterRoot; + + final packageConfigExtraData = packageConfig.extraData; + if (pubspec.dependencies["flutter"] != null && + packageConfigExtraData is Map) { + final root = packageConfigExtraData['flutterRoot'] as String?; + if (root != null) { + flutterRoot = Uri.parse(root); + } + } + + final List projectPlatforms; + if (flutterRoot != null) { + projectPlatforms = OS.values.where((value) { + final platformPath = projectUri.resolve(value.toString()).toFilePath(); + final platformDir = Directory(platformPath); + return platformDir.existsSync(); + }).toList(growable: false); + } else { + projectPlatforms = []; + } + + return NativeDoctor( + options: options, + packageConfig: packageConfig, + pubspec: pubspec, + projectPlatforms: projectPlatforms, + flutterRoot: flutterRoot, + ); + } + + bool get hostOnly { + return projectPlatforms.isEmpty; + } + + List get targetPlatforms { + final systems = switch (OS.current) { + OS.macOS => [OS.macOS, OS.iOS, OS.android], + OS.windows => [OS.windows, OS.android], + OS.linux => [OS.linux, OS.android], + final OS os => throw ToolError('Unsupported host system: $os'), + }; + return systems.where(projectPlatforms.contains).toList(growable: false); + } + + Future run() async { + verboseLogger.level = options.verbose ? Level.ALL : Level.OFF; + + final projectType = flutterRoot != null ? 'Flutter' : 'Dart'; + writer.printMessage( + 'Project: ${writer.bolden(pubspec.name)} ($projectType)', + ); + final platforms = hostOnly ? 'host only' : targetPlatforms.join(', '); + writer.printMessage('Buildable platforms: ${writer.bolden(platforms)}'); + + writer.emptyLine(); + + final manifests = NativeManifest.forPackageConfig( + packageConfig, + verboseLogger: verboseLogger, + ); + + if (manifests.isEmpty) { + writer.printMessage( + 'No dependency containing ${NativeManifest.fileName} found. Nothing to check.', + ); + writer.emptyLine(); + return; + } + + final checkers = [ + NdkToolchainChecker(doctor: this), + RustToolchainChecker(doctor: this), + ]; + + final proposedActions = <(String, ProposedAction)>[]; + + for (final checker in checkers) { + for (final manifest in manifests) { + final context = ManifestContext( + manifestVersion: manifest.version, + ); + final node = manifest.requirements[checker.manifestKey]; + if (node != null) { + await checker.updateFromManifest(context, node); + } + } + + final result = await checker.validate(); + if (result != null) { + writer.printMessage( + 'Native toolchain: ${writer.bolden(checker.displayName)}'); + writer.emptyLine(); + for (final section in result.sections) { + final prefix = section.ok ? '[✓]' : '[✗]'; + final color = section.ok ? TextColor.green : TextColor.red; + writer.printMessage( + section.title, + prefix: ' $prefix ', + prefixColor: color, + ); + for (final message in section.messages) { + final prefix = message.ok ? '•' : '!'; + final color = message.ok ? TextColor.green : TextColor.red; + writer.printMessage( + message.message, + prefix: ' $prefix ', + prefixColor: color, + ); + } + } + writer.emptyLine(); + proposedActions.addAll( + result.proposedActions.map( + (action) => (checker.displayName, action), + ), + ); + } + } + + if (proposedActions.isNotEmpty) { + writer.printMessage( + writer.color( + writer.bolden('Proposed actions:'), + TextColor.cyan, + ), + ); + writer.emptyLine(); + + final maxCheckerLength = + maxBy(proposedActions.map((action) => action.$1.length), (a) => a); + for (final (checkerName, action) in proposedActions) { + writer.printMessage( + action.description, + prefix: + ' • ($checkerName) '.padRight(11 - 4 + maxCheckerLength!, ' '), + prefixColor: TextColor.cyan, + ); + } + + writer.emptyLine(); + if (!writer.hasTerminal && !options.yes) { + writer.printMessage( + 'Run native doctor in terminal or with ${writer.bolden('-y')} to perform proposed actions.', + ); + return; + } + if (!options.yes) { + writer.printMessage( + writer.bolden( + 'Do you want native doctor to perform proposed actions? (y/N)'), + ); + final input = stdin.readLineSync(); + if (input != 'y') { + return; + } + } + + for (final checker in checkers) { + await checker.fix(writer); + } + } else { + writer.printMessage(writer.bolden('No issues found!')); + } + + writer.emptyLine(); + } +} + +const String version = '0.0.1'; + +ArgParser buildParser() { + return ArgParser() + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Print this usage information.', + ) + ..addFlag( + 'verbose', + abbr: 'v', + negatable: false, + help: 'Show additional command output.', + ) + ..addFlag( + 'yes', + abbr: 'y', + negatable: false, + help: 'Answer yes to all prompts.', + ) + ..addFlag( + 'version', + negatable: false, + help: 'Print the tool version.', + ) + ..addOption( + 'path', + help: 'Path to project root (optional)', + ); +} + +void printUsage(ArgParser argParser, Writer writer) { + writer.printMessage('Usage: native_doctor [arguments]'); + writer.printMessage(argParser.usage, prefix: ' '); +} + +void run(List arguments) async { + final writer = AnsiWriter(); + final ArgParser argParser = buildParser(); + try { + final ArgResults results = argParser.parse(arguments); + + // Process the parsed arguments. + if (results.wasParsed('help')) { + printUsage(argParser, writer); + return; + } + if (results.wasParsed('version')) { + print('native_doctor version: $version'); + return; + } + + final options = ToolOptions( + verbose: results.wasParsed('verbose'), + yes: results.wasParsed('yes'), + path: results.wasParsed('path') + ? Directory(results['path']) + : Directory.current, + writer: writer, + ); + + if (options.path.existsSync()) { + final doctor = await NativeDoctor.withOptions(options); + doctor.run(); + } else { + throw ToolError('Project path ${options.path.path} does not exist.'); + } + } on FormatException catch (e) { + // Print usage information if an invalid argument was provided. + writer.printMessage(e.message); + writer.emptyLine(); + printUsage(argParser, writer); + } on Exception catch (e) { + writer.emptyLine(); + writer.printMessage(writer.color( + 'Native doctor failed with error:', + TextColor.red, + )); + writer.emptyLine(); + writer.printMessage(e.toString()); + exit(1); + } +} diff --git a/native_doctor/lib/src/tool_error.dart b/native_doctor/lib/src/tool_error.dart new file mode 100644 index 0000000..700159c --- /dev/null +++ b/native_doctor/lib/src/tool_error.dart @@ -0,0 +1,8 @@ +class ToolError implements Exception { + final String message; + + ToolError(this.message); + + @override + String toString() => message; +} diff --git a/native_doctor/lib/src/toolchain_checker.dart b/native_doctor/lib/src/toolchain_checker.dart new file mode 100644 index 0000000..1ed3df1 --- /dev/null +++ b/native_doctor/lib/src/toolchain_checker.dart @@ -0,0 +1,65 @@ +import 'package:pub_semver/pub_semver.dart'; +import 'package:yaml/yaml.dart'; + +class ManifestContext { + ManifestContext({ + required this.manifestVersion, + }); + + final Version manifestVersion; +} + +class ValidationResultSectionMessage { + ValidationResultSectionMessage({ + required this.ok, + required this.message, + }); + + final bool ok; + final String message; +} + +class ValidationResultSection { + ValidationResultSection({ + required this.ok, + required this.title, + required this.messages, + }); + + final bool ok; + final String title; + final List messages; +} + +class ProposedAction { + ProposedAction({required this.description}); + + final String description; +} + +class ValidationResult { + final List sections; + final List proposedActions; + + ValidationResult({ + required this.sections, + required this.proposedActions, + }); +} + +abstract class ActionLogger { + Future logAction(String message, Future Function() action); +} + +abstract class ToolchainChecker { + String get manifestKey; + String get displayName; + + Future updateFromManifest( + ManifestContext context, + YamlNode manifestNode, + ); + + Future validate(); + Future fix(ActionLogger logger); +} diff --git a/native_doctor/lib/src/writer.dart b/native_doctor/lib/src/writer.dart new file mode 100644 index 0000000..35b9883 --- /dev/null +++ b/native_doctor/lib/src/writer.dart @@ -0,0 +1,190 @@ +import 'dart:async'; +import 'dart:io' as io; +import 'dart:math' as math; + +import 'package:native_doctor/src/toolchain_checker.dart'; + +enum TextColor { + red, + green, + blue, + cyan, + yellow, + magenta, + grey, +} + +abstract class TextStyler { + String bolden(String message); + String color(String message, TextColor color); +} + +abstract class Writer implements TextStyler, ActionLogger { + void printMessage( + String message, { + String prefix = '', + TextColor? prefixColor, + }); + void emptyLine(); + bool get hasTerminal; + bool get supportsEmoji; +} + +class AnsiWriter implements Writer, ActionLogger { + AnsiWriter() { + if (hasTerminal) { + wrapWidth = math.min(io.stdout.terminalColumns, 100); + } else { + wrapWidth = 100; + } + } + + late int wrapWidth; + + @override + bool get hasTerminal => io.stdout.hasTerminal; + + bool get supportsAnsi => io.stdout.supportsAnsiEscapes; + + // Assume unicode emojis are supported when not on Windows. + // If we are on Windows, unicode emojis are supported in Windows Terminal, + // which sets the WT_SESSION environment variable. See: + // https://learn.microsoft.com/en-us/windows/terminal/tips-and-tricks + @override + bool get supportsEmoji => + !io.Platform.isWindows || + io.Platform.environment.containsKey('WT_SESSION'); + + static const String bold = '\u001B[1m'; + static const String resetBold = '\u001B[22m'; + + static const String red = '\u001b[31m'; + static const String green = '\u001b[32m'; + static const String blue = '\u001b[34m'; + static const String cyan = '\u001b[36m'; + static const String magenta = '\u001b[35m'; + static const String yellow = '\u001b[33m'; + static const String grey = '\u001b[90m'; + static const String resetColor = '\u001B[39m'; + + @override + String bolden(String message) { + if (supportsAnsi) { + return '$bold$message$resetBold'; + } else { + return message; + } + } + + @override + String color(String message, TextColor color) { + if (supportsAnsi) { + final colorAnsi = switch (color) { + TextColor.red => red, + TextColor.green => green, + TextColor.blue => blue, + TextColor.cyan => cyan, + TextColor.magenta => magenta, + TextColor.yellow => yellow, + TextColor.grey => grey, + }; + return '$colorAnsi$message$resetColor'; + } else { + return message; + } + } + + @override + void printMessage( + String message, { + String prefix = '', + TextColor? prefixColor, + }) { + if (message.contains('\n')) { + final lines = message.split('\n'); + for (final line in lines) { + printMessage(line, prefix: prefix, prefixColor: prefixColor); + } + return; + } + + final words = message.split(' '); + final buffer = StringBuffer(); + + buffer.write(prefixColor == null ? prefix : color(prefix, prefixColor)); + + var firstWord = true; + for (final word in words) { + if (buffer.length + word.length + 1 > wrapWidth) { + print(buffer.toString()); + buffer.clear(); + buffer.write(' ' * prefix.length); + firstWord = true; + } + if (buffer.isNotEmpty && !firstWord) { + buffer.write(' '); + } + buffer.write(word); + firstWord = false; + } + if (buffer.isNotEmpty) { + print(buffer.toString()); + } + } + + @override + void emptyLine() { + print(''); + } + + void _writeToStdOut(String message) => io.stdout.write(message); + + Timer? _spinerTimmer; + + late String _animation; + int _animationFrame = 0; + + void _timerCallback(Timer timer) { + _writeToStdOut('\b${_animation[_animationFrame]}'); + _animationFrame = (_animationFrame + 1) % _animation.length; + } + + void _beginSpinner() { + _spinerTimmer?.cancel(); + if (hasTerminal) { + _animation = supportsEmoji ? '⠙⠚⠖⠦⢤⣠⣄⡤⠴⠲⠓⠋' : r'-\|/'; + _animationFrame = 0; + _writeToStdOut(' '); + _spinerTimmer = Timer.periodic( + const Duration(milliseconds: 100), + _timerCallback, + ); + } + } + + void _endSpinner() { + if (_spinerTimmer != null) { + _spinerTimmer?.cancel(); + _writeToStdOut('\b'); + } + } + + @override + Future logAction(String message, Future Function() action) async { + _writeToStdOut(color(' • ', TextColor.yellow)); + _writeToStdOut(message); + _writeToStdOut(' '); + _beginSpinner(); + try { + await action(); + _endSpinner(); + _writeToStdOut(color('[done]', TextColor.green)); + _writeToStdOut('\n'); + } catch (e) { + _endSpinner(); + _writeToStdOut(color('[failed]', TextColor.red)); + _writeToStdOut('\n'); + rethrow; + } + } +} diff --git a/native_doctor/pubspec.lock b/native_doctor/pubspec.lock new file mode 100644 index 0000000..6953c18 --- /dev/null +++ b/native_doctor/pubspec.lock @@ -0,0 +1,459 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + 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" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + url: "https://pub.dev" + source: hosted + version: "1.7.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + 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: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + 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: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b" + url: "https://pub.dev" + source: hosted + version: "1.14.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + native_assets_cli: + dependency: "direct main" + description: + name: native_assets_cli + sha256: "9c1b67ccf85ec9282f34e5348ae78dcb7da2c7dc965c0265306477d977853a0d" + url: "https://pub.dev" + source: hosted + version: "0.5.4" + native_toolchain_rust_common: + dependency: "direct main" + description: + path: "../native_toolchain_rust_common" + relative: true + source: path + version: "0.1.0-dev.2" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: "direct main" + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + pub_semver: + dependency: "direct main" + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: "direct main" + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + rustup: + dependency: "direct main" + description: + path: "../rustup" + relative: true + source: path + version: "0.1.0-dev.2" + 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: transitive + 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: d72b538180efcf8413cd2e4e6fcc7ae99c7712e0909eb9223f9da6e6d0ef715f + url: "https://pub.dev" + source: hosted + version: "1.25.4" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4d070a6bc36c1c4e89f20d353bfd71dc30cdf2bd0e14349090af360a029ab292" + url: "https://pub.dev" + source: hosted + version: "0.6.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48 + url: "https://pub.dev" + source: hosted + version: "14.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.4.0-265.0.dev <4.0.0" diff --git a/native_doctor/pubspec.yaml b/native_doctor/pubspec.yaml new file mode 100644 index 0000000..8863811 --- /dev/null +++ b/native_doctor/pubspec.yaml @@ -0,0 +1,28 @@ +name: native_doctor +description: A tool that analyzes Flutter and Dart project dependencies, find requirements for native toolchains and, if possible, resolves issues. +version: 0.1.0-dev.3 +repository: https://github.com/irondash/native_toolchain_rust + +environment: + sdk: ^3.4.0-265.0.dev + +executables: + native_doctor: + +# Add regular dependencies here. +dependencies: + native_toolchain_rust_common: ^0.1.0-dev.2 + args: ^2.4.2 + package_config: ^2.1.0 + rustup: ^0.1.0-dev.2 + pub_semver: ^2.1.4 + yaml: ^3.1.2 + pubspec_parse: ^1.2.3 + native_assets_cli: ^0.5.4 + collection: ^1.18.0 + logging: ^1.2.0 + path: ^1.9.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.25.4 diff --git a/native_doctor/test/android_environment_test.dart b/native_doctor/test/android_environment_test.dart new file mode 100644 index 0000000..e55a80a --- /dev/null +++ b/native_doctor/test/android_environment_test.dart @@ -0,0 +1,32 @@ +import 'package:native_doctor/src/android_sdk.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:test/test.dart'; + +void main() { + test('android environment', () async { + final processManager = FakeProcessManager.list([ + FakeCommand( + command: ['flutter', 'config', '--machine'], + stdout: ''' +{ + "enable-macos-desktop": true, + "enable-linux-desktop": true, + "cli-animations": false, + "enable-native-assets": true, + "android-studio-dir": "/Applications/Android Studio.app/Contents", + "android-sdk": "/Users/Matej/Library/Android/sdk", + "jdk-dir": "/Applications/Android Studio.app/Contents/jbr/Contents/Home" +} +''', + ) + ]); + await withProcessManager(processManager, () async { + final androidSdkInfo = await AndroidSdkInfo.find(); + expect(androidSdkInfo, isNotNull); + expect(androidSdkInfo!.androidSdk, '/Users/Matej/Library/Android/sdk'); + expect(androidSdkInfo.javaHome, + '/Applications/Android Studio.app/Contents/jbr/Contents/Home'); + expect(processManager, hasNoRemainingExpectations); + }); + }); +} diff --git a/native_doctor/test/ndk.dart b/native_doctor/test/ndk.dart new file mode 100644 index 0000000..8c65f48 --- /dev/null +++ b/native_doctor/test/ndk.dart @@ -0,0 +1,28 @@ +import 'dart:io'; + +import 'package:native_doctor/src/checkers/ndk.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + test('Load a file', () async { + final file = File('test/sdkmanager.txt').readAsStringSync(); + final stableVersion = NdkToolchainChecker.findBestNdkKversion( + sdkManagerOutput: file, + minimumVersion: Version(21, 0, 6113669), + ); + expect(stableVersion, Version(26, 3, 11579264)); + + final prereleaseVersion = NdkToolchainChecker.findBestNdkKversion( + sdkManagerOutput: file, + minimumVersion: Version(26, 4, 0), + ); + expect(prereleaseVersion, Version(27, 0, 11718014)); + + final noVersion = NdkToolchainChecker.findBestNdkKversion( + sdkManagerOutput: file, + minimumVersion: Version(28, 0, 0), + ); + expect(noVersion, isNull); + }); +} diff --git a/native_doctor/test/sdkmanager.txt b/native_doctor/test/sdkmanager.txt new file mode 100644 index 0000000..cfd17ab --- /dev/null +++ b/native_doctor/test/sdkmanager.txt @@ -0,0 +1,460 @@ +Loading package information... Loading local repository... [========= ] 25% Loading local repository... [========= ] 25% Fetch remote repository... [========== ] 26% Fetch remote repository... [============ ] 31% Fetch remote repository... [============ ] 32% Fetch remote repository... [============= ] 33% Fetch remote repository... [============= ] 35% Fetch remote repository... [============== ] 36% Fetch remote repository... [============== ] 37% Fetch remote repository... [=============== ] 38% Fetch remote repository... [=============== ] 39% Fetch remote repository... [================ ] 40% Fetch remote repository... [================ ] 41% Fetch remote repository... [================ ] 42% Fetch remote repository... [================= ] 44% Fetch remote repository... [================= ] 45% Fetch remote repository... [================= ] 45% Fetch remote repository... [================= ] 45% Fetch remote repository... [================== ] 46% Fetch remote repository... [================== ] 47% Fetch remote repository... [=================== ] 48% Fetch remote repository... [=================== ] 49% Fetch remote repository... [==================== ] 50% Fetch remote repository... [==================== ] 51% Fetch remote repository... [==================== ] 53% Fetch remote repository... [===================== ] 54% Fetch remote repository... [===================== ] 55% Fetch remote repository... [====================== ] 56% Fetch remote repository... [====================== ] 57% Fetch remote repository... [======================= ] 58% Fetch remote repository... [======================= ] 59% Fetch remote repository... [======================== ] 60% Fetch remote repository... [======================== ] 61% Fetch remote repository... [========================= ] 63% Fetch remote repository... [========================= ] 64% Fetch remote repository... [========================= ] 65% Fetch remote repository... [========================== ] 66% Fetch remote repository... [========================== ] 67% Fetch remote repository... [=========================== ] 68% Fetch remote repository... [=========================== ] 69% Fetch remote repository... [============================ ] 70% Fetch remote repository... [============================ ] 72% Fetch remote repository... [============================= ] 73% Fetch remote repository... [============================= ] 74% Fetch remote repository... [============================= ] 75% Fetch remote repository... [============================= ] 75% Computing updates... [=======================================] 100% Computing updates... +Installed packages: + Path | Version | Description | Location + ------- | ------- | ------- | ------- + build-tools;29.0.2 | 29.0.2 | Android SDK Build-Tools 29.0.2 | build-tools/29.0.2 + build-tools;30.0.2 | 30.0.2 | Android SDK Build-Tools 30.0.2 | build-tools/30.0.2 + build-tools;30.0.3 | 30.0.3 | Android SDK Build-Tools 30.0.3 | build-tools/30.0.3 + build-tools;32.0.0 | 32.0.0 | Android SDK Build-Tools 32 | build-tools/32.0.0 + build-tools;32.1.0-rc1 | 32.1.0 rc1 | Android SDK Build-Tools 32.1-rc1 | build-tools/32.1.0-rc1 + cmake;3.18.1 | 3.18.1 | CMake 3.18.1 | cmake/3.18.1 + cmake;3.22.1 | 3.22.1 | CMake 3.22.1 | cmake/3.22.1 + cmdline-tools;latest | 9.0 | Android SDK Command-line Tools (latest) | cmdline-tools/latest + emulator | 32.1.12 | Android Emulator | emulator + ndk;23.1.7779620 | 23.1.7779620 | NDK (Side by side) 23.1.7779620 | ndk/23.1.7779620 + ndk;25.1.8937393 | 25.1.8937393 | NDK (Side by side) 25.1.8937393 | ndk/25.1.8937393 + patcher;v4 | 1 | SDK Patch Applier v4 | patcher/v4 + platform-tools | 34.0.1 | Android SDK Platform-Tools | platform-tools + platforms;android-27 | 3 | Android SDK Platform 27 | platforms/android-27 + platforms;android-28 | 6 | Android SDK Platform 28 | platforms/android-28 + platforms;android-29 | 5 | Android SDK Platform 29 | platforms/android-29 + platforms;android-30 | 3 | Android SDK Platform 30 | platforms/android-30 + platforms;android-31 | 1 | Android SDK Platform 31 | platforms/android-31 + platforms;android-32 | 1 | Android SDK Platform 32 | platforms/android-32 + platforms;android-33 | 2 | Android SDK Platform 33 | platforms/android-33 + platforms;android-34 | 2 | Android SDK Platform 34 | platforms/android-34 + skiaparser;2 | 3 | Layout Inspector image server for API S | skiaparser/2 + sources;android-31 | 1 | Sources for Android 31 | sources/android-31 + system-images;android-23;google_apis;arm64-v8a | 33 | Google APIs ARM 64 v8a System Image | system-images/android-23/google_apis/arm64-v8a + system-images;android-23;google_apis;x86_64 | 33 | Google APIs Intel x86 Atom_64 System Image | system-images/android-23/google_apis/x86_64 + system-images;android-24;default;arm64-v8a | 9 | ARM 64 v8a System Image | system-images/android-24/default/arm64-v8a + system-images;android-25;google_apis;armeabi-v7a | 18 | Google APIs ARM EABI v7a System Image | system-images/android-25/google_apis/armeabi-v7a + system-images;android-26;default;arm64-v8a | 1 | ARM 64 v8a System Image | system-images/android-26/default/arm64-v8a + system-images;android-29;google_apis;arm64-v8a | 12 | Google APIs ARM 64 v8a System Image | system-images/android-29/google_apis/arm64-v8a + system-images;android-30;google_apis_playstore;arm64-v8a | 10 | Google Play ARM 64 v8a System Image | system-images/android-30/google_apis_playstore/arm64-v8a + system-images;android-30;google_atd;x86 | 1 | Google APIs ATD Intel x86 Atom System Image | system-images/android-30/google_atd/x86 + system-images;android-31;google_apis;arm64-v8a | 10 | Google APIs ARM 64 v8a System Image | system-images/android-31/google_apis/arm64-v8a + system-images;android-32;google_apis;arm64-v8a | 6 | Google APIs ARM 64 v8a System Image | system-images/android-32/google_apis/arm64-v8a + system-images;android-32;google_apis_playstore;arm64-v8a | 3 | Google Play ARM 64 v8a System Image | system-images/android-32/google_apis_playstore/arm64-v8a + +Available Packages: + Path | Version | Description + ------- | ------- | ------- + add-ons;addon-google_apis-google-15 | 3 | Google APIs + add-ons;addon-google_apis-google-16 | 4 | Google APIs + add-ons;addon-google_apis-google-17 | 4 | Google APIs + add-ons;addon-google_apis-google-18 | 4 | Google APIs + add-ons;addon-google_apis-google-19 | 20 | Google APIs + add-ons;addon-google_apis-google-21 | 1 | Google APIs + add-ons;addon-google_apis-google-22 | 1 | Google APIs + add-ons;addon-google_apis-google-23 | 1 | Google APIs + add-ons;addon-google_apis-google-24 | 1 | Google APIs + build-tools;19.1.0 | 19.1.0 | Android SDK Build-Tools 19.1 + build-tools;20.0.0 | 20.0.0 | Android SDK Build-Tools 20 + build-tools;21.1.2 | 21.1.2 | Android SDK Build-Tools 21.1.2 + build-tools;22.0.1 | 22.0.1 | Android SDK Build-Tools 22.0.1 + build-tools;23.0.1 | 23.0.1 | Android SDK Build-Tools 23.0.1 + build-tools;23.0.2 | 23.0.2 | Android SDK Build-Tools 23.0.2 + build-tools;23.0.3 | 23.0.3 | Android SDK Build-Tools 23.0.3 + build-tools;24.0.0 | 24.0.0 | Android SDK Build-Tools 24 + build-tools;24.0.1 | 24.0.1 | Android SDK Build-Tools 24.0.1 + build-tools;24.0.2 | 24.0.2 | Android SDK Build-Tools 24.0.2 + build-tools;24.0.3 | 24.0.3 | Android SDK Build-Tools 24.0.3 + build-tools;25.0.0 | 25.0.0 | Android SDK Build-Tools 25 + build-tools;25.0.1 | 25.0.1 | Android SDK Build-Tools 25.0.1 + build-tools;25.0.2 | 25.0.2 | Android SDK Build-Tools 25.0.2 + build-tools;25.0.3 | 25.0.3 | Android SDK Build-Tools 25.0.3 + build-tools;26.0.0 | 26.0.0 | Android SDK Build-Tools 26 + build-tools;26.0.1 | 26.0.1 | Android SDK Build-Tools 26.0.1 + build-tools;26.0.2 | 26.0.2 | Android SDK Build-Tools 26.0.2 + build-tools;26.0.3 | 26.0.3 | Android SDK Build-Tools 26.0.3 + build-tools;27.0.0 | 27.0.0 | Android SDK Build-Tools 27 + build-tools;27.0.1 | 27.0.1 | Android SDK Build-Tools 27.0.1 + build-tools;27.0.2 | 27.0.2 | Android SDK Build-Tools 27.0.2 + build-tools;27.0.3 | 27.0.3 | Android SDK Build-Tools 27.0.3 + build-tools;28.0.0 | 28.0.0 | Android SDK Build-Tools 28 + build-tools;28.0.1 | 28.0.1 | Android SDK Build-Tools 28.0.1 + build-tools;28.0.2 | 28.0.2 | Android SDK Build-Tools 28.0.2 + build-tools;28.0.3 | 28.0.3 | Android SDK Build-Tools 28.0.3 + build-tools;29.0.0 | 29.0.0 | Android SDK Build-Tools 29 + build-tools;29.0.1 | 29.0.1 | Android SDK Build-Tools 29.0.1 + build-tools;29.0.2 | 29.0.2 | Android SDK Build-Tools 29.0.2 + build-tools;29.0.3 | 29.0.3 | Android SDK Build-Tools 29.0.3 + build-tools;30.0.0 | 30.0.0 | Android SDK Build-Tools 30 + build-tools;30.0.1 | 30.0.1 | Android SDK Build-Tools 30.0.1 + build-tools;30.0.2 | 30.0.2 | Android SDK Build-Tools 30.0.2 + build-tools;30.0.3 | 30.0.3 | Android SDK Build-Tools 30.0.3 + build-tools;31.0.0 | 31.0.0 | Android SDK Build-Tools 31 + build-tools;32.0.0 | 32.0.0 | Android SDK Build-Tools 32 + build-tools;32.1.0-rc1 | 32.1.0 rc1 | Android SDK Build-Tools 32.1-rc1 + build-tools;33.0.0 | 33.0.0 | Android SDK Build-Tools 33 + build-tools;33.0.1 | 33.0.1 | Android SDK Build-Tools 33.0.1 + build-tools;33.0.2 | 33.0.2 | Android SDK Build-Tools 33.0.2 + build-tools;33.0.3 | 33.0.3 | Android SDK Build-Tools 33.0.3 + build-tools;34.0.0 | 34.0.0 | Android SDK Build-Tools 34 + build-tools;34.0.0-rc1 | 34.0.0 rc1 | Android SDK Build-Tools 34-rc1 + build-tools;34.0.0-rc2 | 34.0.0 rc2 | Android SDK Build-Tools 34-rc2 + build-tools;34.0.0-rc3 | 34.0.0 rc3 | Android SDK Build-Tools 34-rc3 + build-tools;35.0.0-rc1 | 35.0.0 rc1 | Android SDK Build-Tools 35-rc1 + build-tools;35.0.0-rc2 | 35.0.0 rc2 | Android SDK Build-Tools 35-rc2 + build-tools;35.0.0-rc3 | 35.0.0 rc3 | Android SDK Build-Tools 35-rc3 + cmake;3.10.2.4988404 | 3.10.2 | CMake 3.10.2.4988404 + cmake;3.18.1 | 3.18.1 | CMake 3.18.1 + cmake;3.22.1 | 3.22.1 | CMake 3.22.1 + cmake;3.6.4111459 | 3.6.4111459 | CMake 3.6.4111459 + cmdline-tools;1.0 | 1.0 | Android SDK Command-line Tools + cmdline-tools;10.0 | 10.0 | Android SDK Command-line Tools + cmdline-tools;11.0 | 11.0 | Android SDK Command-line Tools + cmdline-tools;12.0 | 12.0 | Android SDK Command-line Tools + cmdline-tools;13.0 | 13.0 | Android SDK Command-line Tools + cmdline-tools;2.1 | 2.1 | Android SDK Command-line Tools + cmdline-tools;3.0 | 3.0 | Android SDK Command-line Tools + cmdline-tools;4.0 | 4.0 | Android SDK Command-line Tools + cmdline-tools;5.0 | 5.0 | Android SDK Command-line Tools + cmdline-tools;6.0 | 6.0 | Android SDK Command-line Tools + cmdline-tools;7.0 | 7.0 | Android SDK Command-line Tools + cmdline-tools;8.0 | 8.0 | Android SDK Command-line Tools + cmdline-tools;9.0 | 9.0 | Android SDK Command-line Tools + cmdline-tools;latest | 13.0 | Android SDK Command-line Tools (latest) + emulator | 34.1.20 | Android Emulator + extras;android;m2repository | 47.0.0 | Android Support Repository + extras;google;auto | 2.0 | Android Auto Desktop Head Unit Emulator + extras;google;google_play_services | 49 | Google Play services + extras;google;instantapps | 1.9.0 | Google Play Instant Development SDK + extras;google;m2repository | 58 | Google Repository + extras;google;market_apk_expansion | 1 | Google Play APK Expansion library + extras;google;market_licensing | 1 | Google Play Licensing Library + extras;google;simulators | 1 | Android Auto API Simulators + extras;google;webdriver | 2 | Google Web Driver + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0 | 1 | Solver for ConstraintLayout 1.0.0 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-alpha4 | 1 | com.android.support.constraint:constraint-layout-solver:1.0.0-alpha4 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-alpha8 | 1 | Solver for ConstraintLayout 1.0.0-alpha8 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta1 | 1 | Solver for ConstraintLayout 1.0.0-beta1 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta2 | 1 | Solver for ConstraintLayout 1.0.0-beta2 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta3 | 1 | Solver for ConstraintLayout 1.0.0-beta3 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta4 | 1 | Solver for ConstraintLayout 1.0.0-beta4 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.0-beta5 | 1 | Solver for ConstraintLayout 1.0.0-beta5 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.1 | 1 | Solver for ConstraintLayout 1.0.1 + extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.2 | 1 | Solver for ConstraintLayout 1.0.2 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0 | 1 | ConstraintLayout for Android 1.0.0 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-alpha4 | 1 | com.android.support.constraint:constraint-layout:1.0.0-alpha4 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-alpha8 | 1 | ConstraintLayout for Android 1.0.0-alpha8 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-beta1 | 1 | ConstraintLayout for Android 1.0.0-beta1 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-beta2 | 1 | ConstraintLayout for Android 1.0.0-beta2 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-beta3 | 1 | ConstraintLayout for Android 1.0.0-beta3 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-beta4 | 1 | ConstraintLayout for Android 1.0.0-beta4 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.0-beta5 | 1 | ConstraintLayout for Android 1.0.0-beta5 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.1 | 1 | ConstraintLayout for Android 1.0.1 + extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2 | 1 | ConstraintLayout for Android 1.0.2 + ndk-bundle | 22.1.7171670 | NDK + ndk;16.1.4479499 | 16.1.4479499 | NDK (Side by side) 16.1.4479499 + ndk;17.2.4988734 | 17.2.4988734 | NDK (Side by side) 17.2.4988734 + ndk;18.1.5063045 | 18.1.5063045 | NDK (Side by side) 18.1.5063045 + ndk;19.2.5345600 | 19.2.5345600 | NDK (Side by side) 19.2.5345600 + ndk;20.0.5594570 | 20.0.5594570 | NDK (Side by side) 20.0.5594570 + ndk;20.1.5948944 | 20.1.5948944 | NDK (Side by side) 20.1.5948944 + ndk;21.1.6352462 | 21.1.6352462 | NDK (Side by side) 21.1.6352462 + ndk;21.2.6472646 | 21.2.6472646 | NDK (Side by side) 21.2.6472646 + ndk;21.3.6528147 | 21.3.6528147 | NDK (Side by side) 21.3.6528147 + ndk;21.4.7075529 | 21.4.7075529 | NDK (Side by side) 21.4.7075529 + ndk;22.0.7026061 | 22.0.7026061 | NDK (Side by side) 22.0.7026061 + ndk;22.1.7171670 | 22.1.7171670 | NDK (Side by side) 22.1.7171670 + ndk;23.0.7599858 | 23.0.7599858 | NDK (Side by side) 23.0.7599858 + ndk;23.1.7779620 | 23.1.7779620 | NDK (Side by side) 23.1.7779620 + ndk;23.2.8568313 | 23.2.8568313 | NDK (Side by side) 23.2.8568313 + ndk;24.0.8215888 | 24.0.8215888 | NDK (Side by side) 24.0.8215888 + ndk;25.0.8775105 | 25.0.8775105 | NDK (Side by side) 25.0.8775105 + ndk;25.1.8937393 | 25.1.8937393 | NDK (Side by side) 25.1.8937393 + ndk;25.2.9519653 | 25.2.9519653 | NDK (Side by side) 25.2.9519653 + ndk;26.0.10792818 | 26.0.10792818 | NDK (Side by side) 26.0.10792818 + ndk;26.1.10909125 | 26.1.10909125 | NDK (Side by side) 26.1.10909125 + ndk;26.2.11394342 | 26.2.11394342 | NDK (Side by side) 26.2.11394342 + ndk;26.3.11579264 | 26.3.11579264 | NDK (Side by side) 26.3.11579264 + ndk;27.0.11718014 | 27.0.11718014 rc1 | NDK (Side by side) 27.0.11718014 + platform-tools | 35.0.1 | Android SDK Platform-Tools + platforms;android-10 | 2 | Android SDK Platform 10 + platforms;android-11 | 2 | Android SDK Platform 11 + platforms;android-12 | 3 | Android SDK Platform 12 + platforms;android-13 | 1 | Android SDK Platform 13 + platforms;android-14 | 4 | Android SDK Platform 14 + platforms;android-15 | 5 | Android SDK Platform 15 + platforms;android-16 | 5 | Android SDK Platform 16 + platforms;android-17 | 3 | Android SDK Platform 17 + platforms;android-18 | 3 | Android SDK Platform 18 + platforms;android-19 | 4 | Android SDK Platform 19 + platforms;android-20 | 2 | Android SDK Platform 20 + platforms;android-21 | 2 | Android SDK Platform 21 + platforms;android-22 | 2 | Android SDK Platform 22 + platforms;android-23 | 3 | Android SDK Platform 23 + platforms;android-24 | 2 | Android SDK Platform 24 + platforms;android-25 | 3 | Android SDK Platform 25 + platforms;android-26 | 2 | Android SDK Platform 26 + platforms;android-27 | 3 | Android SDK Platform 27 + platforms;android-28 | 6 | Android SDK Platform 28 + platforms;android-29 | 5 | Android SDK Platform 29 + platforms;android-30 | 3 | Android SDK Platform 30 + platforms;android-31 | 1 | Android SDK Platform 31 + platforms;android-32 | 1 | Android SDK Platform 32 + platforms;android-33 | 3 | Android SDK Platform 33 + platforms;android-33-ext4 | 1 | Android SDK Platform 33-ext4 + platforms;android-33-ext5 | 1 | Android SDK Platform 33-ext5 + platforms;android-34 | 3 | Android SDK Platform 34 + platforms;android-34-ext11 | 1 | Android SDK Platform 34-ext11 + platforms;android-34-ext8 | 1 | Android SDK Platform 34-ext8 + platforms;android-7 | 3 | Android SDK Platform 7 + platforms;android-8 | 3 | Android SDK Platform 8 + platforms;android-9 | 2 | Android SDK Platform 9 + platforms;android-TiramisuPrivacySandbox | 9 | Android SDK Platform TiramisuPrivacySandbox + platforms;android-UpsideDownCakePrivacySandbox | 3 | Android SDK Platform UpsideDownCakePrivacySandbox + platforms;android-VanillaIceCream | 3 | Android SDK Platform VanillaIceCream + skiaparser;1 | 6 | Layout Inspector image server for API 29-30 + skiaparser;2 | 3 | Layout Inspector image server for API S + skiaparser;3 | 4 | Layout Inspector image server for API 31-35 + sources;android-15 | 2 | Sources for Android 15 + sources;android-16 | 2 | Sources for Android 16 + sources;android-17 | 1 | Sources for Android 17 + sources;android-18 | 1 | Sources for Android 18 + sources;android-19 | 2 | Sources for Android 19 + sources;android-20 | 1 | Sources for Android 20 + sources;android-21 | 1 | Sources for Android 21 + sources;android-22 | 1 | Sources for Android 22 + sources;android-23 | 1 | Sources for Android 23 + sources;android-24 | 1 | Sources for Android 24 + sources;android-25 | 1 | Sources for Android 25 + sources;android-26 | 1 | Sources for Android 26 + sources;android-27 | 1 | Sources for Android 27 + sources;android-28 | 1 | Sources for Android 28 + sources;android-29 | 1 | Sources for Android 29 + sources;android-30 | 1 | Sources for Android 30 + sources;android-31 | 1 | Sources for Android 31 + sources;android-32 | 1 | Sources for Android 32 + sources;android-33 | 1 | Sources for Android 33 + sources;android-34 | 2 | Sources for Android 34 + system-images;android-10;default;armeabi-v7a | 5 | ARM EABI v7a System Image + system-images;android-10;default;x86 | 5 | Intel x86 Atom System Image + system-images;android-10;google_apis;armeabi-v7a | 6 | Google APIs ARM EABI v7a System Image + system-images;android-10;google_apis;x86 | 6 | Google APIs Intel x86 Atom System Image + system-images;android-14;default;armeabi-v7a | 2 | ARM EABI v7a System Image + system-images;android-15;default;armeabi-v7a | 5 | ARM EABI v7a System Image + system-images;android-15;default;x86 | 7 | Intel x86 Atom System Image + system-images;android-15;google_apis;armeabi-v7a | 6 | Google APIs ARM EABI v7a System Image + system-images;android-15;google_apis;x86 | 7 | Google APIs Intel x86 Atom System Image + system-images;android-16;default;armeabi-v7a | 6 | ARM EABI v7a System Image + system-images;android-16;default;mips | 1 | MIPS System Image + system-images;android-16;default;x86 | 7 | Intel x86 Atom System Image + system-images;android-16;google_apis;armeabi-v7a | 6 | Google APIs ARM EABI v7a System Image + system-images;android-16;google_apis;x86 | 7 | Google APIs Intel x86 Atom System Image + system-images;android-17;default;armeabi-v7a | 6 | ARM EABI v7a System Image + system-images;android-17;default;mips | 1 | MIPS System Image + system-images;android-17;default;x86 | 7 | Intel x86 Atom System Image + system-images;android-17;google_apis;armeabi-v7a | 6 | Google APIs ARM EABI v7a System Image + system-images;android-17;google_apis;x86 | 7 | Google APIs Intel x86 Atom System Image + system-images;android-18;default;armeabi-v7a | 5 | ARM EABI v7a System Image + system-images;android-18;default;x86 | 4 | Intel x86 Atom System Image + system-images;android-18;google_apis;armeabi-v7a | 6 | Google APIs ARM EABI v7a System Image + system-images;android-18;google_apis;x86 | 6 | Google APIs Intel x86 Atom System Image + system-images;android-19;default;armeabi-v7a | 5 | ARM EABI v7a System Image + system-images;android-19;default;x86 | 6 | Intel x86 Atom System Image + system-images;android-19;google_apis;armeabi-v7a | 40 | Google APIs ARM EABI v7a System Image + system-images;android-19;google_apis;x86 | 40 | Google APIs Intel x86 Atom System Image + system-images;android-21;android-tv;armeabi-v7a | 3 | Android TV ARM EABI v7a System Image + system-images;android-21;android-tv;x86 | 3 | Android TV Intel x86 Atom System Image + system-images;android-21;default;arm64-v8a | 4 | ARM 64 v8a System Image + system-images;android-21;default;armeabi-v7a | 4 | ARM EABI v7a System Image + system-images;android-21;default;x86 | 5 | Intel x86 Atom System Image + system-images;android-21;default;x86_64 | 5 | Intel x86_64 Atom System Image + system-images;android-21;google_apis;arm64-v8a | 32 | Google APIs ARM 64 v8a System Image + system-images;android-21;google_apis;armeabi-v7a | 32 | Google APIs ARM EABI v7a System Image + system-images;android-21;google_apis;x86 | 32 | Google APIs Intel x86 Atom System Image + system-images;android-21;google_apis;x86_64 | 32 | Google APIs Intel x86_64 Atom System Image + system-images;android-22;android-tv;x86 | 3 | Android TV Intel x86 Atom System Image + system-images;android-22;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-22;default;armeabi-v7a | 2 | ARM EABI v7a System Image + system-images;android-22;default;x86 | 6 | Intel x86 Atom System Image + system-images;android-22;default;x86_64 | 6 | Intel x86_64 Atom System Image + system-images;android-22;google_apis;arm64-v8a | 26 | Google APIs ARM 64 v8a System Image + system-images;android-22;google_apis;armeabi-v7a | 26 | Google APIs ARM EABI v7a System Image + system-images;android-22;google_apis;x86 | 26 | Google APIs Intel x86 Atom System Image + system-images;android-22;google_apis;x86_64 | 26 | Google APIs Intel x86_64 Atom System Image + system-images;android-23;android-tv;armeabi-v7a | 12 | Android TV ARM EABI v7a System Image + system-images;android-23;android-tv;x86 | 21 | Android TV Intel x86 Atom System Image + system-images;android-23;default;arm64-v8a | 7 | ARM 64 v8a System Image + system-images;android-23;default;armeabi-v7a | 6 | ARM EABI v7a System Image + system-images;android-23;default;x86 | 10 | Intel x86 Atom System Image + system-images;android-23;default;x86_64 | 10 | Intel x86_64 Atom System Image + system-images;android-23;google_apis;arm64-v8a | 33 | Google APIs ARM 64 v8a System Image + system-images;android-23;google_apis;armeabi-v7a | 33 | Google APIs ARM EABI v7a System Image + system-images;android-23;google_apis;x86 | 33 | Google APIs Intel x86 Atom System Image + system-images;android-23;google_apis;x86_64 | 33 | Google APIs Intel x86_64 Atom System Image + system-images;android-24;android-tv;x86 | 22 | Android TV Intel x86 Atom System Image + system-images;android-24;default;arm64-v8a | 9 | ARM 64 v8a System Image + system-images;android-24;default;armeabi-v7a | 7 | ARM EABI v7a System Image + system-images;android-24;default;x86 | 8 | Intel x86 Atom System Image + system-images;android-24;default;x86_64 | 8 | Intel x86_64 Atom System Image + system-images;android-24;google_apis;arm64-v8a | 29 | Google APIs ARM 64 v8a System Image + system-images;android-24;google_apis;x86 | 27 | Google APIs Intel x86 Atom System Image + system-images;android-24;google_apis;x86_64 | 27 | Google APIs Intel x86_64 Atom System Image + system-images;android-24;google_apis_playstore;x86 | 19 | Google Play Intel x86 Atom System Image + system-images;android-25;android-tv;x86 | 16 | Android TV Intel x86 Atom System Image + system-images;android-25;android-wear;armeabi-v7a | 3 | Android Wear ARM EABI v7a System Image + system-images;android-25;android-wear;x86 | 3 | Android Wear Intel x86 Atom System Image + system-images;android-25;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-25;default;x86 | 1 | Intel x86 Atom System Image + system-images;android-25;default;x86_64 | 1 | Intel x86_64 Atom System Image + system-images;android-25;google_apis;arm64-v8a | 20 | Google APIs ARM 64 v8a System Image + system-images;android-25;google_apis;armeabi-v7a | 18 | Google APIs ARM EABI v7a System Image + system-images;android-25;google_apis;x86 | 18 | Google APIs Intel x86 Atom System Image + system-images;android-25;google_apis;x86_64 | 18 | Google APIs Intel x86_64 Atom System Image + system-images;android-25;google_apis_playstore;x86 | 9 | Google Play Intel x86 Atom System Image + system-images;android-26;android-tv;x86 | 14 | Android TV Intel x86 Atom System Image + system-images;android-26;android-wear;x86 | 4 | Android Wear Intel x86 Atom System Image + system-images;android-26;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-26;default;x86 | 1 | Intel x86 Atom System Image + system-images;android-26;default;x86_64 | 1 | Intel x86_64 Atom System Image + system-images;android-26;google_apis;arm64-v8a | 3 | Google APIs ARM 64 v8a System Image + system-images;android-26;google_apis;x86 | 16 | Google APIs Intel x86 Atom System Image + system-images;android-26;google_apis;x86_64 | 16 | Google APIs Intel x86_64 Atom System Image + system-images;android-26;google_apis_playstore;x86 | 7 | Google Play Intel x86 Atom System Image + system-images;android-27;android-tv;x86 | 9 | Android TV Intel x86 Atom System Image + system-images;android-27;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-27;default;x86 | 1 | Intel x86 Atom System Image + system-images;android-27;default;x86_64 | 1 | Intel x86_64 Atom System Image + system-images;android-27;google_apis;arm64-v8a | 3 | Google APIs ARM 64 v8a System Image + system-images;android-27;google_apis;x86 | 11 | Google APIs Intel x86 Atom System Image + system-images;android-27;google_apis_playstore;x86 | 3 | Google Play Intel x86 Atom System Image + system-images;android-28;android-tv;x86 | 10 | Android TV Intel x86 Atom System Image + system-images;android-28;android-wear;x86 | 9 | Wear OS Intel x86 Atom System Image + system-images;android-28;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-28;default;x86 | 4 | Intel x86 Atom System Image + system-images;android-28;default;x86_64 | 4 | Intel x86_64 Atom System Image + system-images;android-28;google_apis;arm64-v8a | 2 | Google APIs ARM 64 v8a System Image + system-images;android-28;google_apis;x86 | 12 | Google APIs Intel x86 Atom System Image + system-images;android-28;google_apis;x86_64 | 11 | Google APIs Intel x86_64 Atom System Image + system-images;android-28;google_apis_playstore;arm64-v8a | 2 | Google ARM64-V8a Play ARM 64 v8a System Image + system-images;android-28;google_apis_playstore;x86 | 9 | Google Play Intel x86 Atom System Image + system-images;android-28;google_apis_playstore;x86_64 | 8 | Google Play Intel x86_64 Atom System Image + system-images;android-29;android-tv;x86 | 3 | Android TV Intel x86 Atom System Image + system-images;android-29;default;arm64-v8a | 8 | ARM 64 v8a System Image + system-images;android-29;default;x86 | 8 | Intel x86 Atom System Image + system-images;android-29;default;x86_64 | 8 | Intel x86_64 Atom System Image + system-images;android-29;google_apis;arm64-v8a | 13 | Google APIs ARM 64 v8a System Image + system-images;android-29;google_apis;x86 | 12 | Google APIs Intel x86 Atom System Image + system-images;android-29;google_apis;x86_64 | 12 | Google APIs Intel x86_64 Atom System Image + system-images;android-29;google_apis_playstore;arm64-v8a | 9 | Google Play ARM 64 v8a System Image + system-images;android-29;google_apis_playstore;x86 | 8 | Google Play Intel x86 Atom System Image + system-images;android-29;google_apis_playstore;x86_64 | 8 | Google Play Intel x86_64 Atom System Image + system-images;android-30;android-tv;x86 | 4 | Android TV Intel x86 Atom System Image + system-images;android-30;android-wear-cn;arm64-v8a | 12 | China version of Wear OS 3 ARM 64 v8a System Image + system-images;android-30;android-wear-cn;x86 | 12 | China version of Wear OS 3 Intel x86 Atom System Image + system-images;android-30;android-wear;arm64-v8a | 12 | Wear OS 3 ARM 64 v8a System Image + system-images;android-30;android-wear;x86 | 12 | Wear OS 3 Intel x86 Atom System Image + system-images;android-30;aosp_atd;arm64-v8a | 2 | AOSP ATD ARM 64 v8a System Image + system-images;android-30;aosp_atd;x86 | 1 | AOSP ATD Intel x86 Atom System Image + system-images;android-30;aosp_atd;x86_64 | 2 | AOSP ATD Intel x86_64 Atom System Image + system-images;android-30;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-30;default;x86_64 | 11 | Intel x86_64 Atom System Image + system-images;android-30;google-tv;x86 | 4 | Google TV Intel x86 Atom System Image + system-images;android-30;google_apis;arm64-v8a | 13 | Google APIs ARM 64 v8a System Image + system-images;android-30;google_apis;x86 | 12 | Google APIs Intel x86 Atom System Image + system-images;android-30;google_apis;x86_64 | 12 | Google APIs Intel x86_64 Atom System Image + system-images;android-30;google_apis_playstore;arm64-v8a | 10 | Google Play ARM 64 v8a System Image + system-images;android-30;google_apis_playstore;x86 | 9 | Google Play Intel x86 Atom System Image + system-images;android-30;google_apis_playstore;x86_64 | 10 | Google Play Intel x86_64 Atom System Image + system-images;android-30;google_atd;arm64-v8a | 2 | Google APIs ATD ARM 64 v8a System Image + system-images;android-30;google_atd;x86 | 1 | Google APIs ATD Intel x86 Atom System Image + system-images;android-30;google_atd;x86_64 | 2 | Google APIs ATD Intel x86_64 Atom System Image + system-images;android-31;android-tv;arm64-v8a | 4 | Android TV ARM 64 v8a System Image + system-images;android-31;android-tv;x86 | 4 | Android TV Intel x86 Atom System Image + system-images;android-31;aosp_atd;arm64-v8a | 1 | AOSP ATD ARM 64 v8a System Image + system-images;android-31;aosp_atd;x86_64 | 2 | AOSP ATD Intel x86_64 Atom System Image + system-images;android-31;default;arm64-v8a | 4 | ARM 64 v8a System Image + system-images;android-31;default;x86_64 | 5 | Intel x86_64 Atom System Image + system-images;android-31;google-tv;arm64-v8a | 4 | Google TV ARM 64 v8a System Image + system-images;android-31;google-tv;x86 | 4 | Google TV Intel x86 Atom System Image + system-images;android-31;google_apis;arm64-v8a | 11 | Google APIs ARM 64 v8a System Image + system-images;android-31;google_apis;x86_64 | 14 | Google APIs Intel x86_64 Atom System Image + system-images;android-31;google_apis_playstore;arm64-v8a | 9 | Google Play ARM 64 v8a System Image + system-images;android-31;google_apis_playstore;x86_64 | 9 | Google Play Intel x86_64 Atom System Image + system-images;android-31;google_atd;arm64-v8a | 1 | Google APIs ATD ARM 64 v8a System Image + system-images;android-31;google_atd;x86_64 | 2 | Google APIs ATD Intel x86_64 Atom System Image + system-images;android-32;android-desktop;arm64-v8a | 5 | Desktop ARM 64 v8a System Image + system-images;android-32;android-desktop;x86_64 | 5 | Desktop Intel x86_64 Atom System Image + system-images;android-32;aosp_atd;arm64-v8a | 1 | AOSP ATD ARM 64 v8a System Image + system-images;android-32;aosp_atd;x86_64 | 1 | AOSP ATD Intel x86_64 Atom System Image + system-images;android-32;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-32;default;x86_64 | 2 | Intel x86_64 Atom System Image + system-images;android-32;google_apis;arm64-v8a | 8 | Google APIs ARM 64 v8a System Image + system-images;android-32;google_apis;x86_64 | 8 | Google APIs Intel x86_64 Atom System Image + system-images;android-32;google_apis_playstore;arm64-v8a | 4 | Google Play ARM 64 v8a System Image + system-images;android-32;google_apis_playstore;x86_64 | 4 | Google Play Intel x86_64 Atom System Image + system-images;android-32;google_atd;arm64-v8a | 1 | Google APIs ATD ARM 64 v8a System Image + system-images;android-32;google_atd;x86_64 | 1 | Google APIs ATD Intel x86_64 Atom System Image + system-images;android-33-ext4;google_apis_playstore;arm64-v8a | 1 | Google Play ARM 64 v8a System Image + system-images;android-33-ext4;google_apis_playstore;x86_64 | 1 | Google Play Intel x86_64 Atom System Image + system-images;android-33-ext5;google_apis_playstore;arm64-v8a | 1 | Google Play ARM 64 v8a System Image + system-images;android-33-ext5;google_apis_playstore;x86_64 | 1 | Google Play Intel x86_64 Atom System Image + system-images;android-33;android-automotive;arm64-v8a | 2 | Android Automotive with Google APIs ARM 64 v8a System Image + system-images;android-33;android-automotive;x86_64 | 2 | Android Automotive with Google APIs Intel x86_64 Atom System Image + system-images;android-33;android-desktop;arm64-v8a | 3 | Desktop ARM 64 v8a System Image + system-images;android-33;android-desktop;x86_64 | 3 | Desktop Intel x86_64 Atom System Image + system-images;android-33;android-tv;arm64-v8a | 5 | Android TV ARM 64 v8a System Image + system-images;android-33;android-tv;x86 | 5 | Android TV Intel x86 Atom System Image + system-images;android-33;android-wear;arm64-v8a | 3 | Wear OS 4 ARM 64 v8a System Image + system-images;android-33;android-wear;x86_64 | 3 | Wear OS 4 Intel x86_64 Atom System Image + system-images;android-33;aosp_atd;arm64-v8a | 2 | AOSP ATD ARM 64 v8a System Image + system-images;android-33;aosp_atd;x86_64 | 2 | AOSP ATD Intel x86_64 Atom System Image + system-images;android-33;default;arm64-v8a | 2 | ARM 64 v8a System Image + system-images;android-33;default;x86_64 | 2 | Intel x86_64 Atom System Image + system-images;android-33;google-tv;arm64-v8a | 5 | Google TV ARM 64 v8a System Image + system-images;android-33;google-tv;x86 | 5 | Google TV Intel x86 Atom System Image + system-images;android-33;google_apis;arm64-v8a | 15 | Google APIs ARM 64 v8a System Image + system-images;android-33;google_apis;x86_64 | 15 | Google APIs Intel x86_64 Atom System Image + system-images;android-33;google_apis_playstore;arm64-v8a | 7 | Google Play ARM 64 v8a System Image + system-images;android-33;google_apis_playstore;x86_64 | 7 | Google Play Intel x86_64 Atom System Image + system-images;android-33;google_atd;arm64-v8a | 2 | Google APIs ATD ARM 64 v8a System Image + system-images;android-33;google_atd;x86_64 | 2 | Google APIs ATD Intel x86_64 Atom System Image + system-images;android-34-ext10;google_apis_playstore;arm64-v8a | 2 | Google Play ARM 64 v8a System Image + system-images;android-34-ext10;google_apis_playstore;x86_64 | 2 | Google Play Intel x86_64 Atom System Image + system-images;android-34-ext11;google_apis_playstore;arm64-v8a | 1 | Google Play ARM 64 v8a System Image + system-images;android-34-ext11;google_apis_playstore;x86_64 | 1 | Google Play Intel x86_64 Atom System Image + system-images;android-34-ext8;google_apis_playstore;arm64-v8a | 2 | Google Play ARM 64 v8a System Image + system-images;android-34-ext8;google_apis_playstore;x86_64 | 2 | Google Play Intel x86_64 Atom System Image + system-images;android-34;android-tv;arm64-v8a | 2 | Android TV ARM 64 v8a System Image + system-images;android-34;android-tv;x86 | 2 | Android TV Intel x86 Atom System Image + system-images;android-34;aosp_atd;arm64-v8a | 2 | AOSP ATD ARM 64 v8a System Image + system-images;android-34;aosp_atd;x86_64 | 2 | AOSP ATD Intel x86_64 Atom System Image + system-images;android-34;default;arm64-v8a | 4 | ARM 64 v8a System Image + system-images;android-34;default;x86_64 | 4 | Intel x86_64 Atom System Image + system-images;android-34;google-tv;arm64-v8a | 2 | Google TV ARM 64 v8a System Image + system-images;android-34;google-tv;x86 | 2 | Google TV Intel x86 Atom System Image + system-images;android-34;google_apis;arm64-v8a | 13 | Google APIs ARM 64 v8a System Image + system-images;android-34;google_apis;x86_64 | 13 | Google APIs Intel x86_64 Atom System Image + system-images;android-34;google_apis_playstore;arm64-v8a | 13 | Google Play ARM 64 v8a System Image + system-images;android-34;google_apis_playstore;x86_64 | 13 | Google Play Intel x86_64 Atom System Image + system-images;android-34;google_atd;arm64-v8a | 1 | Google APIs ATD ARM 64 System Image + system-images;android-34;google_atd;x86_64 | 1 | Google APIs ATD Intel x86_64 Atom System Image + system-images;android-TiramisuPrivacySandbox;google_apis;arm64-v8a | 1 | Google APIs ARM 64 v8a System Image + system-images;android-TiramisuPrivacySandbox;google_apis;x86_64 | 1 | Google APIs Intel x86_64 Atom System Image + system-images;android-TiramisuPrivacySandbox;google_apis_playstore;arm64-v8a | 9 | Google Play ARM 64 v8a System Image + system-images;android-TiramisuPrivacySandbox;google_apis_playstore;x86_64 | 9 | Google Play Intel x86_64 Atom System Image + system-images;android-UpsideDownCakePrivacySandbox;google_apis_playstore;arm64-v8a | 3 | Google Play ARM 64 v8a System Image + system-images;android-UpsideDownCakePrivacySandbox;google_apis_playstore;x86_64 | 3 | Google Play Intel x86_64 Atom System Image + system-images;android-VanillaIceCream;google_apis;arm64-v8a | 4 | Google APIs ARM 64 v8a System Image + system-images;android-VanillaIceCream;google_apis;x86_64 | 4 | Google APIs Intel x86_64 Atom System Image + system-images;android-VanillaIceCream;google_apis_playstore;arm64-v8a | 4 | Google Play ARM 64 v8a System Image + system-images;android-VanillaIceCream;google_apis_playstore;x86_64 | 4 | Google Play Intel x86_64 Atom System Image + +Available Updates: + ID | Installed | Available + ------- | ------- | ------- + cmdline-tools;latest | 9.0 | 13.0 + emulator | 32.1.12 | 34.1.20 + platform-tools | 34.0.1 | 35.0.1 + platforms;android-33 | 2 | 3 + platforms;android-34 | 2 | 3 + system-images;android-26;default;arm64-v8a | 1 | 2 + system-images;android-29;google_apis;arm64-v8a | 12 | 13 + system-images;android-31;google_apis;arm64-v8a | 10 | 11 + system-images;android-32;google_apis;arm64-v8a | 6 | 8 + system-images;android-32;google_apis_playstore;arm64-v8a | 3 | 4 + diff --git a/native_toolchain_rust/.metadata b/native_toolchain_rust/.metadata new file mode 100644 index 0000000..01c03d3 --- /dev/null +++ b/native_toolchain_rust/.metadata @@ -0,0 +1,10 @@ +# 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 and should not be manually edited. + +version: + revision: "e8f8a8dc17054425abda5f77d3e714a2668f08fe" + channel: "main" + +project_type: package diff --git a/native_toolchain_rust/CHANGELOG.md b/native_toolchain_rust/CHANGELOG.md new file mode 100644 index 0000000..4d68bba --- /dev/null +++ b/native_toolchain_rust/CHANGELOG.md @@ -0,0 +1,10 @@ +## 0.1.0-dev.2 + + - **FEAT**: add flutter package example. + - **FEAT**: add dart example + refactor. + - **FEAT**: add native_doctor. + - **DOCS**: improve documentation (#2). + +## 0.1.0 + +* Initial release diff --git a/native_toolchain_rust/LICENSE b/native_toolchain_rust/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/native_toolchain_rust/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/native_toolchain_rust/README.md b/native_toolchain_rust/README.md new file mode 100644 index 0000000..a41e888 --- /dev/null +++ b/native_toolchain_rust/README.md @@ -0,0 +1,138 @@ +Library to interact with `rustup` and `cargo` when building Dart and Flutter native assets written in Rust. + +Native assets is currently an experimental feature that is only available in Flutter main branch and needs to be enabled through `flutter config`: +``` +flutter config --enable-native-assets +``` + +## Usage + +To build Rust code alongside Flutter or Dart package following steps are required: + +1. Add `native_toolchain_rust` and `native_assets_cli` as a dependency to your project. +``` +dart pub add native_toolchain_rust +dart pub add native_assets_cli +``` + +2. Create a build script at `hook/build.dart`: + +```dart +import 'dart:io'; + +import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +void main(List args) async { + try { + await build(args, (BuildConfig buildConfig, BuildOutput output) async { + final builder = RustBuilder( + // The ID of native assets consists of package name and crate name. + package: '', + crateManifestPath: 'rust/Cargo.toml', + buildConfig: buildConfig, + ); + await builder.run(output: output); + }); + } catch (e) { + // ignore: avoid_print + print(e); + exit(1); + } +} +``` + +This assumes that your Rust code is located in `rust` directory in package root. Crate must be a `cdylib`. + +3. Add `native_manifest.yaml` file to your package root. This step is not strictly necessary, but it will let [`native_doctor`](https://pub.dev/packages/native_doctor) know what the toolchain requirements for your packages are. + +```yaml +version: 0.1.0 +requirements: + ndk: + version: 25.1.8937393 + rust: + stable: + version: 1.77.2 +``` + +To reference native asset library in your code, you can use the `@ffi.DefaultAsset` annotation: + +```dart +@ffi.DefaultAsset('package:/') +library rust; + +import 'dart:ffi' as ffi; + +@ffi.Native() +external int sum( + int a, + int b, +); +``` + +For complete examples see the [example](../example) directory. + +## Using packages with Rust native assets + +Package that has Rust code in it depends on Rust toolchain to be installed on machine of developer that uses the package. To make this as frictionless as possible, `native_toolchain_rust` detects if Rust toolchain is installed and up-do-date, and if not suggests running [`native_doctor`](https://pub.dev/packages/native_doctor) tool to automatically install and configure necessary toolchains. + +For example, when user tries to build your package without having Rust installed, they get the following error message: +``` +Rustup not found. +Please run native_doctor in your project to fix the issue: + +dart pub global activate native_doctor +dart pub global run native_doctor +``` + +And here's output of `native_doctor` run on a computer with no Rust installation and outdated NDK: + +``` +Project: example (Flutter) +Buildable platforms: macos, ios, android + +Native toolchain: NDK + + [✗] NDK installed, but too old + ! Installed versions: 23.1.7779620 + ! Required minimum version: 25.1.8937393 + +Native toolchain: Rust + + [✗] Rustup not found + [✗] Toolchain stable not installed + ! Required minimum version: 1.77.2 + ! Missing targets: arm-linux-androideabi, aarch64-linux-android, i686-linux-android, + x86_64-linux-android, aarch64-apple-ios, x86_64-apple-ios, aarch64-apple-ios-sim, aarch64-apple-darwin, + x86_64-apple-darwin + +Proposed actions: + + • (NDK) Install NDK 25.1.8937393 or newer + • (Rust) Install rustup + • (Rust) Install toolchain stable + • (Rust) Install targets arm-linux-androideabi, aarch64-linux-android, i686-linux-android, + x86_64-linux-android, aarch64-apple-ios, x86_64-apple-ios, aarch64-apple-ios-sim, aarch64-apple-darwin, + x86_64-apple-darwin for toolchain stable + +Do you want native doctor to perform proposed actions? (y/N) +``` + +After confirming, `native_doctor` will automatically install correct NDK version, required Rust toolchain and targets: + +``` + • Fetching NDK list... [done] + • Installing NDK 26.3.11579264 [done] + • Installing rustup [done] + • Installing Rust toolchain stable [done] + • Installing target arm-linux-androideabi for toolchain stable [done] + • Installing target aarch64-linux-android for toolchain stable [done] + • Installing target i686-linux-android for toolchain stable [done] + • Installing target x86_64-linux-android for toolchain stable [done] + • Installing target aarch64-apple-ios for toolchain stable [done] + • Installing target x86_64-apple-ios for toolchain stable [done] + • Installing target aarch64-apple-ios-sim for toolchain stable [done] + • Installing target aarch64-apple-darwin for toolchain stable [done] + • Installing target x86_64-apple-darwin for toolchain stable [done] +``` diff --git a/native_toolchain_rust/analysis_options.yaml b/native_toolchain_rust/analysis_options.yaml new file mode 100644 index 0000000..12e713a --- /dev/null +++ b/native_toolchain_rust/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:lints/recommended.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/native_toolchain_rust/lib/native_toolchain_rust.dart b/native_toolchain_rust/lib/native_toolchain_rust.dart new file mode 100644 index 0000000..e8ee861 --- /dev/null +++ b/native_toolchain_rust/lib/native_toolchain_rust.dart @@ -0,0 +1,2 @@ +export 'rustup.dart'; +export 'src/builder.dart'; diff --git a/native_toolchain_rust/lib/rustup.dart b/native_toolchain_rust/lib/rustup.dart new file mode 100644 index 0000000..15ae94f --- /dev/null +++ b/native_toolchain_rust/lib/rustup.dart @@ -0,0 +1 @@ +export 'package:rustup/rustup.dart'; diff --git a/native_toolchain_rust/lib/src/android_environment.dart b/native_toolchain_rust/lib/src/android_environment.dart new file mode 100644 index 0000000..453c012 --- /dev/null +++ b/native_toolchain_rust/lib/src/android_environment.dart @@ -0,0 +1,167 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:native_toolchain_rust/rustup.dart'; +import 'package:native_toolchain_rust/src/android_linker_wrapper.dart'; +import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; + +class NdkInfo { + NdkInfo({ + required this.toolchainPath, + required this.ndkVersion, + }); + + /// Path for NDK toolchain bin directory. + final String toolchainPath; + + /// NDK version. + final Version ndkVersion; + + static NdkInfo? forCCompiler(String cCompiler) { + final toolchainPath = path.dirname(cCompiler); + var p = toolchainPath; + while (true) { + final manifest = File(path.join(p, 'source.properties')); + if (manifest.existsSync()) { + final lines = manifest.readAsLinesSync(); + String? pkgDesc; + String? pkgRevision; + for (final line in lines) { + final elements = line.split('='); + if (elements.length != 2) { + continue; + } + final key = elements[0].trim(); + final value = elements[1].trim(); + if (key == 'Pkg.Desc') { + pkgDesc = value; + } else if (key == 'Pkg.Revision') { + pkgRevision = value; + } + } + if (pkgDesc != 'Android NDK' || pkgRevision == null) { + return null; + } + final version = Version.parse(pkgRevision); + return NdkInfo( + toolchainPath: toolchainPath, + ndkVersion: version, + ); + } + final parent = path.normalize(path.join(p, '..')); + if (parent == p) { + break; + } + p = parent; + } + return null; + } +} + +class AndroidEnvironment { + AndroidEnvironment({ + required this.ndkInfo, + required this.minSdkVersion, + required this.targetTempDir, + required this.toolchain, + required this.target, + }); + + /// Info about the NDK being used. + final NdkInfo ndkInfo; + + /// Minimum supported SDK version. + final int minSdkVersion; + + /// Target directory for build artifacts. + final String targetTempDir; + + /// Toolchain being used. + final RustupToolchain toolchain; + + /// Target being built. + final RustTarget target; + + Future> buildEnvironment() async { + final toolchainPath = ndkInfo.toolchainPath; + + final exe = Platform.isWindows ? '.exe' : ''; + + final arKey = 'AR_${target.triple}'; + final arValue = ['${target.triple}-ar', 'llvm-ar', 'llvm-ar.exe'] + .map((e) => path.join(toolchainPath, e)) + .firstWhereOrNull((element) => File(element).existsSync()); + if (arValue == null) { + throw Exception('Failed to find ar for $target in $toolchainPath'); + } + + final targetArg = '--target=${target.triple}$minSdkVersion'; + + final ccKey = 'CC_${target.triple}'; + final ccValue = path.join(toolchainPath, 'clang$exe'); + final cfFlagsKey = 'CFLAGS_${target.triple}'; + final cFlagsValue = targetArg; + + final cxxKey = 'CXX_${target.triple}'; + final cxxValue = path.join(toolchainPath, 'clang++$exe'); + final cxxFlagsKey = 'CXXFLAGS_${target.triple}'; + final cxxFlagsValue = targetArg; + + final linkerKey = + 'cargo_target_${target.triple.replaceAll('-', '_')}_linker' + .toUpperCase(); + + final ranlibKey = 'RANLIB_${target.triple}'; + final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); + + final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; + final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkInfo.ndkVersion); + + final wrapper = AndroidLinkerWrapper( + tempDir: targetTempDir, + toolchain: toolchain, + ); + + return { + arKey: arValue, + ccKey: ccValue, + cfFlagsKey: cFlagsValue, + cxxKey: cxxValue, + cxxFlagsKey: cxxFlagsValue, + ranlibKey: ranlibValue, + rustFlagsKey: rustFlagsValue, + linkerKey: await wrapper.linkerWrapperPath(), + // Recognized by main() so we know when we're acting as a wrapper + '_CARGOKIT_NDK_LINK_TARGET': targetArg, + '_CARGOKIT_NDK_LINK_CLANG': ccValue, + }; + } + + // Workaround for libgcc missing in NDK23, inspired by cargo-ndk + String _libGccWorkaround(String buildDir, Version ndkVersion) { + final workaroundDir = path.join( + buildDir, + 'cargokit', + 'libgcc_workaround', + '${ndkVersion.major}', + ); + Directory(workaroundDir).createSync(recursive: true); + if (ndkVersion.major >= 23) { + File(path.join(workaroundDir, 'libgcc.a')) + .writeAsStringSync('INPUT(-lunwind)'); + } else { + // Other way around, untested, forward libgcc.a from libunwind once Rust + // gets updated for NDK23+. + File(path.join(workaroundDir, 'libunwind.a')) + .writeAsStringSync('INPUT(-lgcc)'); + } + + var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; + if (rustFlags.isNotEmpty) { + rustFlags = '$rustFlags\x1f'; + } + rustFlags = '$rustFlags-L\x1f$workaroundDir'; + return rustFlags; + } +} diff --git a/native_toolchain_rust/lib/src/android_linker_wrapper.dart b/native_toolchain_rust/lib/src/android_linker_wrapper.dart new file mode 100644 index 0000000..fbae8fa --- /dev/null +++ b/native_toolchain_rust/lib/src/android_linker_wrapper.dart @@ -0,0 +1,90 @@ +import 'dart:io'; + +import 'package:native_toolchain_rust/rustup.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:path/path.dart' as path; + +const _cargoToml = ''' +[workspace] + +[package] +name = "linker_wrapper" +version = "0.1.0" +edition = "2021" + +[dependencies] +'''; + +const _cargoLock = ''' +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "linker_wrapper" +version = "0.1.0" +'''; + +const _mainRs = ''' +fn main() { + let args = std::env::args_os().skip(1); + let clang = std::env::var("_CARGOKIT_NDK_LINK_CLANG") + .expect("linker_wrapper: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); + let target = std::env::var("_CARGOKIT_NDK_LINK_TARGET") + .expect("linker_wrapper: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); + + let mut child = std::process::Command::new(&clang) + .arg(target) + .args(args) + .spawn() + .unwrap_or_else(|err| { + eprintln!("linker_wrapper: Failed to spawn {clang:?} as linker: {err}"); + std::process::exit(1) + }); + let status = child.wait().unwrap_or_else(|err| { + eprintln!("cargokit (as linker): Failed to wait for {clang:?} to complete: {err}"); + std::process::exit(1); + }); + + std::process::exit(status.code().unwrap_or(1)) +} +'''; + +class AndroidLinkerWrapper { + final String tempDir; + final RustupToolchain toolchain; + + AndroidLinkerWrapper({ + required this.tempDir, + required this.toolchain, + }); + + Future linkerWrapperPath() async { + String wrapperRoot = path.join(tempDir, 'linker_wrapper_1.0'); + final exe = Platform.isWindows ? '.exe' : ''; + String executablePath = + path.join(wrapperRoot, 'target', 'debug', 'linker_wrapper$exe'); + if (!File(executablePath).existsSync()) { + Directory(wrapperRoot).createSync(recursive: true); + File(path.join(wrapperRoot, 'Cargo.toml')).writeAsStringSync(_cargoToml); + File(path.join(wrapperRoot, 'Cargo.lock')).writeAsStringSync(_cargoLock); + Directory(path.join(wrapperRoot, 'src')).createSync(); + File(path.join(wrapperRoot, 'src', 'main.rs')).writeAsStringSync(_mainRs); + await runCommand( + toolchain.rustup.executablePath, + [ + 'run', + toolchain.name, + 'cargo', + 'build', + '--quiet', + ], + workingDirectory: wrapperRoot, + ); + if (!File(executablePath).existsSync()) { + throw Exception('Failed to build linker wrapper'); + } + } + return executablePath; + } +} diff --git a/native_toolchain_rust/lib/src/builder.dart b/native_toolchain_rust/lib/src/builder.dart new file mode 100644 index 0000000..71cbc47 --- /dev/null +++ b/native_toolchain_rust/lib/src/builder.dart @@ -0,0 +1,275 @@ +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_rust/rustup.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:rustup/rustup.dart'; +import 'package:native_toolchain_rust/src/android_environment.dart'; +import 'package:native_toolchain_rust/src/crate_manifest.dart'; + +import 'package:path/path.dart' as path; + +class RustToolchainException implements Exception { + RustToolchainException({required this.message}); + + final String message; + + @override + String toString() { + return message; + } +} + +class RunNativeDoctorException extends RustToolchainException { + RunNativeDoctorException({required super.message}); + + @override + String toString() { + return '\n' + '$message\n' + 'Please run native_doctor in your project to fix the issue:\n' + '-----------------------------------------------------------\n' + 'dart pub global activate native_doctor\n' + 'dart pub global run native_doctor\n' + '-----------------------------------------------------------'; + } +} + +class RustToolchain { + final RustupToolchain toolchain; + final String name; + + RustToolchain._({ + required this.name, + required this.toolchain, + }); + + static Future withName(String name) async { + final rustup = Rustup.systemRustup(); + if (rustup == null) { + throw RunNativeDoctorException(message: 'Rustup not found.'); + } + final toolchain = await rustup.getToolchain(name); + if (toolchain == null) { + throw RunNativeDoctorException( + message: 'Rust toolchain $name not found.'); + } + return RustToolchain._(name: toolchain.name, toolchain: toolchain); + } + + static RustToolchain withRustupToolchain(RustupToolchain toolchain) { + return RustToolchain._(name: toolchain.name, toolchain: toolchain); + } + + Future _checkTarget({ + required RustTarget target, + }) async { + final installedTargets = await toolchain.installedTargets(); + if (!installedTargets.contains(target)) { + throw RunNativeDoctorException( + message: + 'Rust target ${target.triple} not installed for toolchain $name.', + ); + } + } + + Future _checkNativeManifest({ + required BuildConfig buildConfig, + }) async { + final manifest = NativeManifest.forPackage(buildConfig.packageRoot); + if (manifest == null) { + throw RustToolchainException( + message: '`native_manifest.yaml` expected in package root.\n' + 'See https://pub.dev/packages/native_doctor more information and example manifest.', + ); + } + final requirements = manifest.requirements; + final rustInfo = RustManifestInfo.parse(requirements['rust']!); + for (final toolchainInfo in rustInfo.toolchainToVersion.entries) { + if (toolchain.name.startsWith(toolchainInfo.key)) { + final requiredVersion = toolchainInfo.value; + final installedVersion = await toolchain.rustVersion(); + if (installedVersion < requiredVersion) { + throw RunNativeDoctorException( + message: + 'Rust toolchain $name is older than required version $requiredVersion.', + ); + } + } + } + } +} + +class RustBuilder { + RustBuilder({ + required this.package, + this.toolchain, + required this.crateManifestPath, + required this.buildConfig, + this.ignoreMissingNativeManifest = false, + this.dartBuildFiles = const ['hook/build.dart'], + this.logger, + }); + + /// Custom Rust toolchain to use (optional). + final RustToolchain? toolchain; + + /// Package name. This will be part of asset Id. + /// For example package `my_package` with crate name `my_crate` will have + /// asset id `package:my_package/my_crate`: + /// ```dart + /// @ffi.DefaultAsset('package:my_package/my_crate') + /// library rust; + /// ``` + final String package; + + /// Path to the `Cargo.toml` file relative to the package root. + final String crateManifestPath; + + /// Build config provided to the build callback from `native_assets_cli`. + final BuildConfig buildConfig; + + /// Dart build files inside hook directory that should be added as + /// dependencies. Default value adds `hook/build.dart` as dependency. + final List dartBuildFiles; + + /// By default `native_toolchain_rust` expects `native_manifest.yaml` in + /// package root in order to check for required Rust version and also for + /// `native_doctor` to work. If you don't want to include `native_manifest.yaml` + /// in your package, set this to `true`. + /// + /// See https://pub.dev/packages/native_doctor for more information. + final bool ignoreMissingNativeManifest; + + /// Optional logger for verbose output. + final Logger? logger; + + Future run({required BuildOutput output}) async { + final toolchain = this.toolchain ?? await RustToolchain.withName('stable'); + + final manifestPath = buildConfig.packageRoot.resolve( + crateManifestPath, + ); + final manifestInfo = CrateManifestInfo.load(manifestPath); + final outDir = + buildConfig.outputDirectory.resolve('native_toolchain_rust/'); + + final dylibName = + buildConfig.targetOS.dylibFileName(manifestInfo.packageName); + + if (buildConfig.dryRun) { + output.addAsset(NativeCodeAsset( + package: package, + name: manifestInfo.packageName, + linkMode: DynamicLoadingBundled(), + os: buildConfig.targetOS, + file: Uri.file(dylibName), + )); + return; + } + + final target = RustTarget.from( + architecture: buildConfig.targetArchitecture!, + os: buildConfig.targetOS, + iosSdk: buildConfig.targetOS == OS.iOS ? buildConfig.targetIOSSdk : null, + )!; + + await toolchain._checkTarget(target: target); + if (!ignoreMissingNativeManifest) { + await toolchain._checkNativeManifest(buildConfig: buildConfig); + } + + if (!buildConfig.dryRun) { + await toolchain.toolchain.rustup.runCommand( + [ + 'run', + toolchain.name, + 'cargo', + 'build', + '--manifest-path', + manifestPath.toFilePath(), + '-p', + manifestInfo.packageName, + if (buildConfig.buildMode == BuildMode.release) '--release', + '--target', + target.triple, + '--target-dir', + outDir.toFilePath(), + ], + environment: await _buildEnvironment( + outDir, + target, + toolchain.toolchain, + ), + logger: logger, + ); + } + + final effectiveOutDir = outDir + .resolve('${target.triple}/') + .resolve('${buildConfig.buildMode.name}/'); + + final asset = NativeCodeAsset( + package: package, + name: manifestInfo.packageName, + os: buildConfig.targetOS, + architecture: buildConfig.targetArchitecture, + linkMode: DynamicLoadingBundled(), + file: effectiveOutDir.resolve(dylibName), + ); + output.addAsset(asset); + if (!buildConfig.dryRun) { + _addDependencies( + output: output, + effectiveOutDir: effectiveOutDir, + dylibName: dylibName, + ); + } + for (final source in dartBuildFiles) { + output.addDependency( + buildConfig.packageRoot.resolve(source), + ); + } + } + + void _addDependencies({ + required BuildOutput output, + required Uri effectiveOutDir, + required String dylibName, + }) { + final dylibPath = effectiveOutDir.resolve(dylibName).toFilePath(); + final depFile = path.setExtension(dylibPath, '.d'); + final lines = File(depFile).readAsLinesSync(); + for (final line in lines) { + final parts = line.split(':'); + if (parts[0] == dylibPath) { + final dependencies = parts[1].trim().split(' '); + for (final dependency in dependencies) { + output.addDependency(Uri.file(dependency)); + } + } + } + } + + Future> _buildEnvironment( + Uri outDir, + RustTarget target, + RustupToolchain toolchain, + ) async { + if (buildConfig.targetOS == OS.android) { + final ndkInfo = + NdkInfo.forCCompiler(buildConfig.cCompiler.compiler!.toFilePath())!; + final env = AndroidEnvironment( + ndkInfo: ndkInfo, + minSdkVersion: buildConfig.targetAndroidNdkApi!, + targetTempDir: outDir.toFilePath(), + toolchain: toolchain, + target: target, + ); + return env.buildEnvironment(); + } else { + return {}; + } + } +} diff --git a/native_toolchain_rust/lib/src/crate_manifest.dart b/native_toolchain_rust/lib/src/crate_manifest.dart new file mode 100644 index 0000000..52ff92c --- /dev/null +++ b/native_toolchain_rust/lib/src/crate_manifest.dart @@ -0,0 +1,45 @@ +import 'dart:io'; + +import 'package:toml/toml.dart'; + +class ManifestException { + ManifestException(this.message, {required this.fileName}); + + final String? fileName; + final String message; + + @override + String toString() { + if (fileName != null) { + return 'Failed to parse package manifest at $fileName: $message'; + } else { + return 'Failed to parse package manifest: $message'; + } + } +} + +class CrateManifestInfo { + CrateManifestInfo({required this.packageName}); + + final String packageName; + + static CrateManifestInfo parseManifest(String manifest, + {final String? fileName}) { + final toml = TomlDocument.parse(manifest); + final package = toml.toMap()['package']; + if (package == null) { + throw ManifestException('Missing package section', fileName: fileName); + } + final name = package['name']; + if (name == null) { + throw ManifestException('Missing package name', fileName: fileName); + } + return CrateManifestInfo(packageName: name); + } + + static CrateManifestInfo load(Uri manifestPath) { + final manifestFile = File.fromUri(manifestPath); + final manifest = manifestFile.readAsStringSync(); + return parseManifest(manifest, fileName: manifestFile.path); + } +} diff --git a/native_toolchain_rust/pubspec.lock b/native_toolchain_rust/pubspec.lock new file mode 100644 index 0000000..77449f9 --- /dev/null +++ b/native_toolchain_rust/pubspec.lock @@ -0,0 +1,451 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + args: + dependency: transitive + 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" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + url: "https://pub.dev" + source: hosted + version: "1.7.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + 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: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + 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: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + native_assets_cli: + dependency: "direct main" + description: + name: native_assets_cli + sha256: "9c1b67ccf85ec9282f34e5348ae78dcb7da2c7dc965c0265306477d977853a0d" + url: "https://pub.dev" + source: hosted + version: "0.5.4" + native_toolchain_rust_common: + dependency: "direct main" + description: + path: "../native_toolchain_rust_common" + relative: true + source: path + version: "0.1.0-dev.2" + 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: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: "direct main" + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + pub_semver: + dependency: "direct main" + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + rustup: + dependency: "direct main" + description: + path: "../rustup" + relative: true + source: path + version: "0.1.0-dev.2" + 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: transitive + 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: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + url: "https://pub.dev" + source: hosted + version: "1.25.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + test_core: + dependency: transitive + description: + name: test_core + sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + toml: + dependency: "direct main" + description: + name: toml + sha256: "9968de24e45b632bf1a654fe1ac7b6fe5261c349243df83fd262397799c45a2d" + url: "https://pub.dev" + source: hosted + version: "0.15.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + url: "https://pub.dev" + source: hosted + version: "14.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.4.0-190.0.dev <4.0.0" diff --git a/native_toolchain_rust/pubspec.yaml b/native_toolchain_rust/pubspec.yaml new file mode 100644 index 0000000..d430d2a --- /dev/null +++ b/native_toolchain_rust/pubspec.yaml @@ -0,0 +1,24 @@ +name: native_toolchain_rust +description: Library to interact with rustup and cargo when building Dart and Flutter native assets written in Rust. +version: 0.1.0-dev.2 +homepage: https://github.com/irondash/native_toolchain_rust.git +publish_to: none + +environment: + sdk: ">=3.4.0-190.0.dev <4.0.0" + +dependencies: + native_toolchain_rust_common: ^0.1.0-dev.2 + collection: ^1.18.0 + logging: ^1.2.0 + native_assets_cli: ^0.5.4 + path: ^1.9.0 + process: ^5.0.2 + pub_semver: ^2.1.4 + toml: ^0.15.0 + rustup: ^0.1.0-dev.2 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.21.0 + diff --git a/native_toolchain_rust/test/android_environment_test.dart b/native_toolchain_rust/test/android_environment_test.dart new file mode 100644 index 0000000..616637e --- /dev/null +++ b/native_toolchain_rust/test/android_environment_test.dart @@ -0,0 +1,23 @@ +import 'dart:io'; + +import 'package:native_toolchain_rust/src/android_environment.dart'; +import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + test('NdkInfo', () { + final tempDir = Directory.systemTemp.createTempSync('ndk_info_test'); + final sourceProperties = path.join(tempDir.path, 'source.properties'); + File(sourceProperties) + .writeAsStringSync('Pkg.Desc=Android NDK\nPkg.Revision=25.1.8937393\n'); + final binDir = path.join(tempDir.path, 'prebuilt', 'darwin-x86_64', 'bin'); + Directory(binDir).createSync(recursive: true); + final cCompiler = path.join(binDir, 'clang'); + final info = NdkInfo.forCCompiler(cCompiler); + expect(info, isNotNull); + expect(info!.toolchainPath, binDir); + expect(info.ndkVersion, Version(25, 1, 8937393)); + tempDir.deleteSync(recursive: true); + }); +} diff --git a/native_toolchain_rust/test/build_test.dart b/native_toolchain_rust/test/build_test.dart new file mode 100644 index 0000000..abf93c7 --- /dev/null +++ b/native_toolchain_rust/test/build_test.dart @@ -0,0 +1,196 @@ +import 'dart:io'; + +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void copyPathSync(String from, String to) { + Directory(to).createSync(recursive: true); + for (final file in Directory(from).listSync(recursive: false)) { + if (file.uri.pathSegments.contains('.dart_tool') || + file.uri.pathSegments.contains('build')) { + continue; + } + + final copyTo = p.join(to, p.relative(file.path, from: from)); + if (file is Directory) { + copyPathSync(file.path, copyTo); + } else if (file is File) { + File(file.path).copySync(copyTo); + } else if (file is Link) { + Link(copyTo).createSync(file.targetSync(), recursive: true); + } + } +} + +Future withFlutterExampleProject( + Future Function(Uri) testFn, +) async { + final tempDir = Directory.systemTemp.createTempSync('buildTest'); + try { + final workspaceRoot = Directory.current.uri.resolve('../'); + + final exampleRoot = workspaceRoot.resolve('example/flutter_package'); + + copyPathSync(exampleRoot.toFilePath(), tempDir.path); + + final pubspecOverrides = """ +dependency_overrides: + flutter_package: + path: ../ + native_toolchain_rust: + path: ${workspaceRoot.resolve('native_toolchain_rust').toFilePath()} + native_toolchain_rust_common: + path: ${workspaceRoot.resolve('native_toolchain_rust_common').toFilePath()} + rustup: + path: ${workspaceRoot.resolve('rustup').toFilePath()} + """; + + File(tempDir.uri.resolve('example/pubspec_overrides.yaml').toFilePath()) + .writeAsStringSync(pubspecOverrides); + + final exampleUri = tempDir.uri.resolve('example/'); + + await runCommand( + 'flutter', + ['clean'], + workingDirectory: exampleUri.toFilePath(), + ); + + await runCommand( + 'flutter', + ['pub', 'get'], + workingDirectory: exampleUri.toFilePath(), + ); + + await testFn(exampleUri); + } finally { + int attempt = 0; + while (true) { + try { + await tempDir.delete(recursive: true); + break; + } catch (e) { + if (attempt > 5) { + rethrow; + } + attempt++; + // Windows being windows. + print('Failed to clean builder temp dir: $e'); + await Future.delayed(const Duration(seconds: 1)); + } + } + } +} + +void main() async { + test( + 'macOS project', + () async { + await withFlutterExampleProject((uri) async { + for (final config in ['Debug', 'Profile', 'Release']) { + await runCommand( + 'flutter', + ['build', 'macos', '--${config.toLowerCase()}'], + workingDirectory: uri.toFilePath(), + ); + + // Check if the library is built + final library = File.fromUri(uri.resolve( + 'build/macos/Build/Products/$config/example.app/Contents/Frameworks/flutter_ffi_plugin.framework/Versions/A/flutter_ffi_plugin')); + expect(library.existsSync(), isTrue); + } + }); + }, + timeout: const Timeout(Duration(minutes: 10)), + skip: !Platform.isMacOS, + ); + test( + 'iOS project', + () async { + await withFlutterExampleProject((uri) async { + for (final config in ['Debug', 'Profile', 'Release']) { + await runCommand( + 'flutter', + ['build', 'ios', '--${config.toLowerCase()}', '--no-codesign'], + workingDirectory: uri.toFilePath(), + ); + + // Check if the library is built + final library = File.fromUri(uri.resolve( + 'build/ios/$config-iphoneos/Runner.app/Frameworks/flutter_ffi_plugin.framework/flutter_ffi_plugin', + )); + expect(library.existsSync(), isTrue); + } + }); + }, + timeout: const Timeout(Duration(minutes: 10)), + skip: !Platform.isMacOS, + ); + test( + 'windows project', + () async { + await withFlutterExampleProject((uri) async { + for (final config in ['Debug', 'Profile', 'Release']) { + await runCommand( + 'flutter', + ['build', 'windows', '--${config.toLowerCase()}'], + workingDirectory: uri.toFilePath(), + ); + + // Check if the library is built + final library = File.fromUri(uri.resolve( + 'build/windows/${Architecture.current}/runner/$config/flutter_ffi_plugin.dll', + )); + expect(library.existsSync(), isTrue); + } + }); + }, + timeout: const Timeout(Duration(minutes: 10)), + skip: !Platform.isWindows, + ); + test( + 'linux project', + () async { + await withFlutterExampleProject((uri) async { + for (final config in ['debug', 'profile', 'release']) { + await runCommand( + 'flutter', + ['build', 'linux', '--$config'], + workingDirectory: uri.toFilePath(), + ); + + // Check if the library is built + final library = File.fromUri(uri.resolve( + 'build/linux/${Architecture.current}/$config/bundle/lib/libflutter_ffi_plugin.so', + )); + expect(library.existsSync(), isTrue); + } + }); + }, + timeout: const Timeout(Duration(minutes: 10)), + skip: !Platform.isLinux, + ); + test( + 'android project', + () async { + await withFlutterExampleProject((uri) async { + for (final config in ['debug', 'profile', 'release']) { + await runCommand( + 'flutter', + ['build', 'apk', '--$config'], + workingDirectory: uri.toFilePath(), + ); + + // Check if the library is built + final library = File.fromUri(uri.resolve( + 'build/app/intermediates/merged_jni_libs/$config/out/arm64-v8a/libflutter_ffi_plugin.so', + )); + expect(library.existsSync(), isTrue); + } + }); + }, + timeout: const Timeout(Duration(minutes: 10)), + ); +} diff --git a/native_toolchain_rust_common/.gitignore b/native_toolchain_rust_common/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/native_toolchain_rust_common/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/native_toolchain_rust_common/CHANGELOG.md b/native_toolchain_rust_common/CHANGELOG.md new file mode 100644 index 0000000..0cc8b51 --- /dev/null +++ b/native_toolchain_rust_common/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.1.0-dev.2 + + - **FEAT**: add dart example + refactor. + +## 0.1.0-dev.1 + +- Initial version. diff --git a/native_toolchain_rust_common/LICENSE b/native_toolchain_rust_common/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/native_toolchain_rust_common/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/native_toolchain_rust_common/README.md b/native_toolchain_rust_common/README.md new file mode 100644 index 0000000..4fca3ee --- /dev/null +++ b/native_toolchain_rust_common/README.md @@ -0,0 +1 @@ +Common functionality for `native_toolchain_rust`, `native_doctor` and `rustup`. diff --git a/native_toolchain_rust_common/analysis_options.yaml b/native_toolchain_rust_common/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/native_toolchain_rust_common/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/native_toolchain_rust_common/lib/native_toolchain_rust_common.dart b/native_toolchain_rust_common/lib/native_toolchain_rust_common.dart new file mode 100644 index 0000000..6247a02 --- /dev/null +++ b/native_toolchain_rust_common/lib/native_toolchain_rust_common.dart @@ -0,0 +1,5 @@ +library; + +export 'src/command.dart'; +export 'src/fake_process_manager.dart'; +export 'src/manifest.dart'; diff --git a/native_toolchain_rust_common/lib/src/command.dart b/native_toolchain_rust_common/lib/src/command.dart new file mode 100644 index 0000000..4ce2120 --- /dev/null +++ b/native_toolchain_rust_common/lib/src/command.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:process/process.dart'; + +const String _kSeparator = "--"; + +ProcessManager _processManager = LocalProcessManager(); + +Future withProcessManager( + ProcessManager processManager, + Future Function() run, +) async { + final previous = _processManager; + _processManager = processManager; + try { + return await run(); + } finally { + _processManager = previous; + } +} + +class CommandFailedException implements Exception { + final String executable; + final List arguments; + final ProcessResult result; + + CommandFailedException({ + required this.executable, + required this.arguments, + required this.result, + }); + + @override + String toString() { + final stdout = result.stdout.toString().trim(); + final stderr = result.stderr.toString().trim(); + return [ + "Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", + "Returned Exit Code: ${result.exitCode}", + _kSeparator, + if (stdout.isNotEmpty) ...[ + "STDOUT:", + stdout, + _kSeparator, + ], + if (stderr.isNotEmpty) ...[ + "STDERR:", + stderr, + ], + ].join('\n'); + } +} + +Future runCommand( + String executable, + List arguments, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, + Logger? logger, +}) async { + logger?.info('Running command $executable ${arguments.join(' ')}'); + + final res = await _processManager.run( + [ + executable, + ...arguments, + ], + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stderrEncoding: stderrEncoding, + stdoutEncoding: stdoutEncoding, + ); + if (res.exitCode != 0) { + throw CommandFailedException( + executable: executable, + arguments: arguments, + result: res, + ); + } else { + return res; + } +} diff --git a/native_toolchain_rust_common/lib/src/fake_process_manager.dart b/native_toolchain_rust_common/lib/src/fake_process_manager.dart new file mode 100644 index 0000000..71a8483 --- /dev/null +++ b/native_toolchain_rust_common/lib/src/fake_process_manager.dart @@ -0,0 +1,535 @@ +// Originally from flutter_tool + +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io' as io + show + Process, + ProcessResult, + ProcessSignal, + ProcessStartMode, + systemEncoding; + +import 'package:file/file.dart'; +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; +import 'package:test/test.dart'; + +export 'package:process/process.dart' show ProcessManager; + +typedef VoidCallback = void Function(); + +/// A command for [FakeProcessManager]. +@immutable +class FakeCommand { + const FakeCommand({ + required this.command, + this.workingDirectory, + this.environment, + this.encoding, + this.duration = Duration.zero, + this.onRun, + this.exitCode = 0, + this.stdout = '', + this.stderr = '', + this.completer, + this.stdin, + this.exception, + this.outputFollowsExit = false, + this.processStartMode, + }); + + /// The exact commands that must be matched for this [FakeCommand] to be + /// considered correct. + final List command; + + /// The exact working directory that must be matched for this [FakeCommand] to + /// be considered correct. + /// + /// If this is null, the working directory is ignored. + final String? workingDirectory; + + /// The environment that must be matched for this [FakeCommand] to be considered correct. + /// + /// If this is null, then the environment is ignored. + /// + /// Otherwise, each key in this environment must be present and must have a + /// value that matches the one given here for the [FakeCommand] to match. + final Map? environment; + + /// The stdout and stderr encoding that must be matched for this [FakeCommand] + /// to be considered correct. + /// + /// If this is null, then the encodings are ignored. + final Encoding? encoding; + + /// The time to allow to elapse before returning the [exitCode], if this command + /// is "executed". + /// + /// If you set this to a non-zero time, you should use a [FakeAsync] zone, + /// otherwise the test will be artificially slow. + final Duration duration; + + /// A callback that is run after [duration] expires but before the [exitCode] + /// (and output) are passed back. + /// + /// The callback will be provided the full command that matched this instance. + /// This can be useful in the rare scenario where the full command cannot be known + /// ahead of time (i.e. when one or more instances of [RegExp] are used to + /// match the command). For example, the command may contain one or more + /// randomly-generated elements, such as a temporary directory path. + final void Function(List command)? onRun; + + /// The process' exit code. + /// + /// To simulate a never-ending process, set [duration] to a value greater than + /// 15 minutes (the timeout for our tests). + /// + /// To simulate a crash, subtract the crash signal number from 256. For example, + /// SIGPIPE (-13) is 243. + final int exitCode; + + /// The output to simulate on stdout. This will be encoded as UTF-8 and + /// returned in one go. + final String stdout; + + /// The output to simulate on stderr. This will be encoded as UTF-8 and + /// returned in one go. + final String stderr; + + /// If provided, allows the command completion to be blocked until the future + /// resolves. + final Completer? completer; + + /// An optional stdin sink that will be exposed through the resulting + /// [FakeProcess]. + final IOSink? stdin; + + /// If provided, this exception will be thrown when the fake command is run. + final Object? exception; + + /// When true, stdout and stderr will only be emitted after the `exitCode` + /// [Future] on [io.Process] completes. + final bool outputFollowsExit; + + final io.ProcessStartMode? processStartMode; + + void _matches( + List command, + String? workingDirectory, + Map? environment, + Encoding? encoding, + io.ProcessStartMode? mode, + ) { + final List matchers = + this.command.map((Pattern x) => x is String ? x : matches(x)).toList(); + expect(command, matchers); + if (processStartMode != null) { + expect(mode, processStartMode); + } + if (this.workingDirectory != null) { + expect(workingDirectory, this.workingDirectory); + } + if (this.environment != null) { + expect(environment, this.environment); + } + if (this.encoding != null) { + expect(encoding, this.encoding); + } + } +} + +/// A fake process for use with [FakeProcessManager]. +/// +/// The process delays exit until both [duration] (if specified) has elapsed +/// and [completer] (if specified) has completed. +/// +/// When [outputFollowsExit] is specified, bytes are streamed to [stderr] and +/// [stdout] after the process exits. +@visibleForTesting +class FakeProcess implements io.Process { + FakeProcess({ + int exitCode = 0, + Duration duration = Duration.zero, + this.pid = 1234, + List stderr = const [], + IOSink? stdin, + List stdout = const [], + Completer? completer, + bool outputFollowsExit = false, + }) : _exitCode = exitCode, + exitCode = Future.delayed(duration).then((void value) { + if (completer != null) { + return completer.future.then((void _) => exitCode); + } + return exitCode; + }), + _stderr = stderr, + stdin = stdin ?? IOSink(StreamController>().sink), + _stdout = stdout, + _completer = completer { + if (_stderr.isEmpty) { + this.stderr = const Stream>.empty(); + } else if (outputFollowsExit) { + // Wait for the process to exit before emitting stderr. + this.stderr = Stream>.fromFuture(this.exitCode.then((_) { + // Return a Future so stderr isn't immediately available to those who + // await exitCode, but is available asynchronously later. + return Future>(() => _stderr); + })); + } else { + this.stderr = Stream>.value(_stderr); + } + + if (_stdout.isEmpty) { + this.stdout = const Stream>.empty(); + } else if (outputFollowsExit) { + // Wait for the process to exit before emitting stdout. + this.stdout = Stream>.fromFuture(this.exitCode.then((_) { + // Return a Future so stdout isn't immediately available to those who + // await exitCode, but is available asynchronously later. + return Future>(() => _stdout); + })); + } else { + this.stdout = Stream>.value(_stdout); + } + } + + /// The process exit code. + final int _exitCode; + + /// When specified, blocks process exit until completed. + final Completer? _completer; + + @override + final Future exitCode; + + @override + final int pid; + + /// The raw byte content of stderr. + final List _stderr; + + @override + late final Stream> stderr; + + @override + final IOSink stdin; + + @override + late final Stream> stdout; + + /// The raw byte content of stdout. + final List _stdout; + + /// The list of [kill] signals this process received so far. + @visibleForTesting + List get signals => _signals; + final List _signals = []; + + @override + bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + _signals.add(signal); + + // Killing a fake process has no effect. + return true; + } +} + +abstract class FakeProcessManager implements ProcessManager { + /// A fake [ProcessManager] which responds to all commands as if they had run + /// instantaneously with an exit code of 0 and no output. + factory FakeProcessManager.any() = _FakeAnyProcessManager; + + /// A fake [ProcessManager] which responds to particular commands with + /// particular results. + /// + /// On creation, pass in a list of [FakeCommand] objects. When the + /// [ProcessManager] methods such as [start] are invoked, the next + /// [FakeCommand] must match (otherwise the test fails); its settings are used + /// to simulate the result of running that command. + /// + /// If no command is found, then one is implied which immediately returns exit + /// code 0 with no output. + /// + /// There is no logic to ensure that all the listed commands are run. Use + /// [FakeCommand.onRun] to set a flag, or specify a sentinel command as your + /// last command and verify its execution is successful, to ensure that all + /// the specified commands are actually called. + factory FakeProcessManager.list(List commands) = + _SequenceProcessManager; + factory FakeProcessManager.empty() => + _SequenceProcessManager([]); + + FakeProcessManager._(); + + /// Adds a new [FakeCommand] to the current process manager. + /// + /// This can be used to configure test expectations after the [ProcessManager] has been + /// provided to another interface. + /// + /// This is a no-op on [FakeProcessManager.any]. + void addCommand(FakeCommand command); + + /// Add multiple [FakeCommand] to the current process manager. + void addCommands(Iterable commands) { + commands.forEach(addCommand); + } + + final Map _fakeRunningProcesses = {}; + + /// Whether this fake has more [FakeCommand]s that are expected to run. + /// + /// This is always `true` for [FakeProcessManager.any]. + bool get hasRemainingExpectations; + + /// The expected [FakeCommand]s that have not yet run. + List get _remainingExpectations; + + @protected + FakeCommand findCommand( + List command, + String? workingDirectory, + Map? environment, + Encoding? encoding, + io.ProcessStartMode? mode, + ); + + int _pid = 9999; + + FakeProcess _runCommand( + List command, { + String? workingDirectory, + Map? environment, + Encoding? encoding, + io.ProcessStartMode? mode, + }) { + _pid += 1; + final FakeCommand fakeCommand = findCommand( + command, + workingDirectory, + environment, + encoding, + mode, + ); + if (fakeCommand.exception != null) { + assert( + fakeCommand.exception is Exception || fakeCommand.exception is Error); + throw fakeCommand.exception!; // ignore: only_throw_errors + } + if (fakeCommand.onRun != null) { + fakeCommand.onRun!(command); + } + return FakeProcess( + duration: fakeCommand.duration, + exitCode: fakeCommand.exitCode, + pid: _pid, + stderr: + encoding?.encode(fakeCommand.stderr) ?? fakeCommand.stderr.codeUnits, + stdin: fakeCommand.stdin, + stdout: + encoding?.encode(fakeCommand.stdout) ?? fakeCommand.stdout.codeUnits, + completer: fakeCommand.completer, + outputFollowsExit: fakeCommand.outputFollowsExit, + ); + } + + @override + Future start( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, // ignored + bool runInShell = false, // ignored + io.ProcessStartMode mode = io.ProcessStartMode.normal, + }) { + final FakeProcess process = _runCommand( + command.cast(), + workingDirectory: workingDirectory, + environment: environment, + encoding: io.systemEncoding, + mode: mode, + ); + if (process._completer != null) { + _fakeRunningProcesses[process.pid] = process; + process.exitCode.whenComplete(() { + _fakeRunningProcesses.remove(process.pid); + }); + } + return Future.value(process); + } + + @override + Future run( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, // ignored + bool runInShell = false, // ignored + Encoding? stdoutEncoding = io.systemEncoding, + Encoding? stderrEncoding = io.systemEncoding, + }) async { + final FakeProcess process = _runCommand( + command.cast(), + workingDirectory: workingDirectory, + environment: environment, + encoding: stdoutEncoding, + ); + await process.exitCode; + return io.ProcessResult( + process.pid, + process._exitCode, + stdoutEncoding == null + ? process._stdout + : await stdoutEncoding.decodeStream(process.stdout), + stderrEncoding == null + ? process._stderr + : await stderrEncoding.decodeStream(process.stderr), + ); + } + + @override + io.ProcessResult runSync( + List command, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, // ignored + bool runInShell = false, // ignored + Encoding? stdoutEncoding = io.systemEncoding, + Encoding? stderrEncoding = io.systemEncoding, + }) { + final FakeProcess process = _runCommand( + command.cast(), + workingDirectory: workingDirectory, + environment: environment, + encoding: stdoutEncoding, + ); + return io.ProcessResult( + process.pid, + process._exitCode, + stdoutEncoding == null + ? process._stdout + : stdoutEncoding.decode(process._stdout), + stderrEncoding == null + ? process._stderr + : stderrEncoding.decode(process._stderr), + ); + } + + /// Returns false if executable in [excludedExecutables]. + @override + bool canRun(dynamic executable, {String? workingDirectory}) => + !excludedExecutables.contains(executable); + + Set excludedExecutables = {}; + + @override + bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + // Killing a fake process has no effect unless it has an attached completer. + final FakeProcess? fakeProcess = _fakeRunningProcesses[pid]; + if (fakeProcess == null) { + return false; + } + fakeProcess.kill(signal); + if (fakeProcess._completer != null) { + fakeProcess._completer.complete(); + } + return true; + } +} + +class _FakeAnyProcessManager extends FakeProcessManager { + _FakeAnyProcessManager() : super._(); + + @override + FakeCommand findCommand( + List command, + String? workingDirectory, + Map? environment, + Encoding? encoding, + io.ProcessStartMode? mode, + ) { + return FakeCommand( + command: command, + workingDirectory: workingDirectory, + environment: environment, + encoding: encoding, + processStartMode: mode, + ); + } + + @override + void addCommand(FakeCommand command) {} + + @override + bool get hasRemainingExpectations => true; + + @override + List get _remainingExpectations => []; +} + +class _SequenceProcessManager extends FakeProcessManager { + _SequenceProcessManager(this._commands) : super._(); + + final List _commands; + + @override + FakeCommand findCommand( + List command, + String? workingDirectory, + Map? environment, + Encoding? encoding, + io.ProcessStartMode? mode, + ) { + expect(_commands, isNotEmpty, + reason: + 'ProcessManager was told to execute $command (in $workingDirectory) ' + 'but the FakeProcessManager.list expected no more processes.'); + _commands.first + ._matches(command, workingDirectory, environment, encoding, mode); + return _commands.removeAt(0); + } + + @override + void addCommand(FakeCommand command) { + _commands.add(command); + } + + @override + bool get hasRemainingExpectations => _commands.isNotEmpty; + + @override + List get _remainingExpectations => _commands; +} + +/// Matcher that successfully matches against a [FakeProcessManager] with +/// no remaining expectations ([item.hasRemainingExpectations] returns false). +const Matcher hasNoRemainingExpectations = _HasNoRemainingExpectations(); + +class _HasNoRemainingExpectations extends Matcher { + const _HasNoRemainingExpectations(); + + @override + bool matches(dynamic item, Map matchState) => + item is FakeProcessManager && !item.hasRemainingExpectations; + + @override + Description describe(Description description) => + description.add('a fake process manager with no remaining expectations'); + + @override + Description describeMismatch( + dynamic item, + Description description, + Map matchState, + bool verbose, + ) { + final FakeProcessManager fakeProcessManager = item as FakeProcessManager; + return description.add( + 'has remaining expectations:\n${fakeProcessManager._remainingExpectations.map((FakeCommand command) => command.command).join('\n')}'); + } +} diff --git a/native_toolchain_rust_common/lib/src/manifest.dart b/native_toolchain_rust_common/lib/src/manifest.dart new file mode 100644 index 0000000..a1adee6 --- /dev/null +++ b/native_toolchain_rust_common/lib/src/manifest.dart @@ -0,0 +1,118 @@ +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:package_config/package_config.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:source_span/source_span.dart'; +import 'package:yaml/yaml.dart'; + +class NdkManifestInfo { + NdkManifestInfo({ + required this.version, + }); + + final Version version; + + static NdkManifestInfo parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('NDK manifest info must be a map', node.span); + } + + final version = node.nodes['version']; + if (version is! YamlScalar) { + throw SourceSpanException('NDK version must be a string', node.span); + } + return NdkManifestInfo( + version: Version.parse(version.value as String), + ); + } +} + +class RustManifestInfo { + RustManifestInfo({required this.toolchainToVersion}); + + final Map toolchainToVersion; + + static RustManifestInfo parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Rust manifest info must be a map', node.span); + } + final toolchainToVersion = {}; + for (final entry in node.nodes.entries) { + final toolchain = entry.key.value as String; + final value = entry.value; + if (value is! YamlMap) { + throw SourceSpanException( + 'Rust toolchain version must be in a map', value.span); + } + final version = value.nodes['version']; + if (version is! YamlScalar) { + throw SourceSpanException('Rust version must be a string', value.span); + } + final parsedVersion = Version.parse(version.value as String); + toolchainToVersion[toolchain] = parsedVersion; + } + + return RustManifestInfo(toolchainToVersion: toolchainToVersion); + } +} + +class NativeManifest { + NativeManifest({ + required this.version, + required this.requirements, + }); + + final Version version; + final Map requirements; + + static NativeManifest parse(String content, {Uri? sourceUri}) { + final doc = loadYamlNode(content, sourceUrl: sourceUri); + if (doc is! YamlMap) { + throw SourceSpanException('Manifest must be a map', doc.span); + } + final versionNode = doc.nodes['version']; + if (versionNode is! YamlScalar) { + throw SourceSpanException('Manifest version must be a string', doc.span); + } + final version = Version.parse(versionNode.value as String); + final requirements = doc.nodes['requirements']; + if (requirements is! YamlMap) { + throw SourceSpanException( + 'Manifest requirements must be a map', doc.span); + } + return NativeManifest( + version: version, + requirements: requirements.nodes.cast(), + ); + } + + static final String fileName = 'native_manifest.yaml'; + + static NativeManifest? forPackage(Uri packageRoot, {Logger? verboseLogger}) { + final manifestUri = packageRoot.resolve(fileName); + final manifestFile = File.fromUri(manifestUri); + if (!manifestFile.existsSync()) { + return null; + } + final manifestContent = manifestFile.readAsStringSync(); + final manifest = + NativeManifest.parse(manifestContent, sourceUri: manifestUri); + verboseLogger?.info('Succesfully parsed manifest at $manifestUri'); + return manifest; + } + + static List forPackageConfig( + PackageConfig packageConfig, { + Logger? verboseLogger, + }) { + final res = []; + for (final package in packageConfig.packages) { + final manifest = forPackage(package.root, verboseLogger: verboseLogger); + if (manifest != null) { + res.add(manifest); + } + } + return res; + } +} diff --git a/native_toolchain_rust_common/pubspec.yaml b/native_toolchain_rust_common/pubspec.yaml new file mode 100644 index 0000000..0679563 --- /dev/null +++ b/native_toolchain_rust_common/pubspec.yaml @@ -0,0 +1,23 @@ +name: native_toolchain_rust_common +description: Common code for native_toolchain_rust and native_doctor. +version: 0.1.0-dev.2 +homepage: https://github.com/irondash/native_toolchain_rust.git + +environment: + sdk: ^3.5.0-90.0.dev + +dependencies: + logging: ^1.2.0 + pub_semver: ^2.1.4 + source_span: ^1.10.0 + package_config: ^2.1.0 + yaml: ^3.1.2 + + # FakeProcessManager + process: ^5.0.2 + file: ^7.0.0 + meta: ^1.12.0 + test: ^1.21.0 + +dev_dependencies: + lints: ^3.0.0 diff --git a/native_toolchain_rust_common/test/manifest_test.dart b/native_toolchain_rust_common/test/manifest_test.dart new file mode 100644 index 0000000..d6fce50 --- /dev/null +++ b/native_toolchain_rust_common/test/manifest_test.dart @@ -0,0 +1,36 @@ +import 'package:native_toolchain_rust_common/src/manifest.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +final _manifest = """ +version: 0.1.0 +requirements: + ndk: + version: 1.2.1 + rust: + stable: + version: 1.1.0 + nightly: + version: 1.0.0 +"""; + +void main() { + test('Manifest can be parsed', () { + final manifest = NativeManifest.parse(_manifest); + expect(manifest.version, Version.parse('0.1.0')); + + final ndkVersion = manifest.requirements['ndk']; + expect(ndkVersion, isNotNull); + final ndkManifestInfo = NdkManifestInfo.parse(ndkVersion!); + expect(ndkManifestInfo.version, Version.parse('1.2.1')); + + final rustVersion = manifest.requirements['rust']; + expect(rustVersion, isNotNull); + + final rustManifestInfo = RustManifestInfo.parse(rustVersion!); + expect(rustManifestInfo.toolchainToVersion, { + 'stable': Version.parse('1.1.0'), + 'nightly': Version.parse('1.0.0'), + }); + }); +} diff --git a/rustup/.gitignore b/rustup/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/rustup/.gitignore @@ -0,0 +1,29 @@ +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/rustup/.metadata b/rustup/.metadata new file mode 100644 index 0000000..109cee7 --- /dev/null +++ b/rustup/.metadata @@ -0,0 +1,10 @@ +# 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 and should not be manually edited. + +version: + revision: "f559f5aee52521e88e1da3b4c0d5f8c25900664b" + channel: "[user-branch]" + +project_type: package diff --git a/rustup/CHANGELOG.md b/rustup/CHANGELOG.md new file mode 100644 index 0000000..0521f94 --- /dev/null +++ b/rustup/CHANGELOG.md @@ -0,0 +1,10 @@ +## 0.1.0-dev.2 + + - **FEAT**: add dart example + refactor. + - **FEAT**: add native_doctor. + - **FEAT**: rustup install and uninstall. + +## 0.1.0-dev.1 + +* Initial release + diff --git a/rustup/LICENSE b/rustup/LICENSE new file mode 100644 index 0000000..5d4e9af --- /dev/null +++ b/rustup/LICENSE @@ -0,0 +1,20 @@ +Copyright 2024 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. diff --git a/rustup/README.md b/rustup/README.md new file mode 100644 index 0000000..32d9ed1 --- /dev/null +++ b/rustup/README.md @@ -0,0 +1,13 @@ +# Rustup + +Dart bindings for the rustup CLI tool. + +## Features + +Rustup API provides the following features: + +- Discovering system `rustup`. +- Downloading and installing `rustup`. +- Querying and installing Rust toolchains. +- Querying and installing Rust targets. + diff --git a/rustup/analysis_options.yaml b/rustup/analysis_options.yaml new file mode 100644 index 0000000..12e713a --- /dev/null +++ b/rustup/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:lints/recommended.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/rustup/lib/rustup.dart b/rustup/lib/rustup.dart new file mode 100644 index 0000000..d1abc03 --- /dev/null +++ b/rustup/lib/rustup.dart @@ -0,0 +1,2 @@ +export 'src/rustup.dart'; +export 'src/installer.dart'; diff --git a/rustup/lib/src/installer.dart b/rustup/lib/src/installer.dart new file mode 100644 index 0000000..7b74662 --- /dev/null +++ b/rustup/lib/src/installer.dart @@ -0,0 +1,126 @@ +import 'dart:io'; + +import 'package:http/http.dart'; +import 'package:logging/logging.dart'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:path/path.dart' as path; + +abstract class RustupInstaller { + static Future create({Logger? logger}) { + if (Platform.isWindows) { + return _WindowsRustupInstaller.prepare(logger: logger); + } else { + return _UnixRustupInstaller.prepare(logger: logger); + } + } + + Future install({ + bool modifyPath = true, + String? cargoHome, + String? rustupHome, + }) async { + final process = await runCommand( + _scriptPath(), + [ + '--default-toolchain', + 'none', + '-y', + if (!modifyPath) '--no-modify-path', + ], + environment: { + if (cargoHome != null) 'CARGO_HOME': cargoHome, + if (rustupHome != null) 'RUSTUP_HOME': rustupHome, + }, + logger: _logger, + ); + final exitCode = process.exitCode; + if (exitCode != 0) { + throw Exception('Failed to install rustup'); + } + } + + Future dispose(); + + RustupInstaller._({ + Logger? logger, + }) : _logger = logger; + + String _scriptPath(); + final Logger? _logger; +} + +class _WindowsRustupInstaller extends RustupInstaller { + _WindowsRustupInstaller({ + required this.tempDirectory, + required this.scriptFile, + required super.logger, + }) : super._(); + + static Future<_WindowsRustupInstaller> prepare({Logger? logger}) async { + final script = await get(Uri.parse('https://win.rustup.rs/x86_64')); + final tempDir = Directory.systemTemp.createTempSync('rustup'); + // Save script to temp dir + final scriptPath = path.join(tempDir.path, 'rustup-init.exe'); + final scriptFile = File(scriptPath); + await scriptFile.writeAsBytes(script.bodyBytes); + return _WindowsRustupInstaller( + tempDirectory: tempDir, + scriptFile: scriptFile, + logger: logger, + ); + } + + @override + Future dispose() async { + tempDirectory.deleteSync(recursive: true); + } + + @override + String _scriptPath() { + return scriptFile.path; + } + + final Directory tempDirectory; + final File scriptFile; +} + +class _UnixRustupInstaller extends RustupInstaller { + _UnixRustupInstaller({ + required this.tempDirectory, + required this.scriptFile, + required super.logger, + }) : super._(); + + static Future<_UnixRustupInstaller> prepare({Logger? logger}) async { + final script = await get(Uri.parse('https://sh.rustup.rs')); + final tempDir = Directory.systemTemp.createTempSync('rustup'); + // Save script to temp dir + final scriptPath = path.join(tempDir.path, 'rustup.sh'); + final scriptFile = File(scriptPath); + await scriptFile.writeAsBytes(script.bodyBytes); + // Make script executable + await runCommand( + 'chmod', + ['+x', scriptPath], + logger: logger, + ); + return _UnixRustupInstaller( + tempDirectory: tempDir, + scriptFile: scriptFile, + logger: logger, + ); + } + + @override + Future dispose() async { + tempDirectory.deleteSync(recursive: true); + } + + @override + String _scriptPath() { + return scriptFile.path; + } + + final Directory tempDirectory; + final File scriptFile; +} diff --git a/rustup/lib/src/mutex.dart b/rustup/lib/src/mutex.dart new file mode 100644 index 0000000..ce0c715 --- /dev/null +++ b/rustup/lib/src/mutex.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +class Mutex { + // Serialize execution of critical sections; For uncontended mutex the execution + // is guaranteed to begin immediately (in this runloop turn) + Future protect(Future Function() criticalSection) async { + while (_locked) { + await _waitUntilUnlocked(); + } + assert(!_locked); + _locked = true; + T res; + try { + _locked = true; + res = await criticalSection(); + } finally { + _locked = false; + _postUnlocked(); + } + return res; + } + + Future _waitUntilUnlocked() { + final c = Completer(); + _after.add(c); + return c.future; + } + + void _postUnlocked() { + if (_after.isNotEmpty) { + final next = _after.removeAt(0); + next.complete(); + } + } + + bool _locked = false; + final _after = []; +} diff --git a/rustup/lib/src/rustup.dart b/rustup/lib/src/rustup.dart new file mode 100644 index 0000000..83acb3a --- /dev/null +++ b/rustup/lib/src/rustup.dart @@ -0,0 +1,312 @@ +import 'dart:io'; +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart' + as common; +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; +import 'package:rustup/src/mutex.dart'; + +final _mutex = Mutex(); + +class Rustup { + /// Returns rustup in user PATH. + static Rustup? systemRustup({Logger? logger}) { + final executablePath = _findExecutablePath(); + return executablePath == null + ? null + : Rustup(executablePath: executablePath, logger: logger); + } + + Rustup({ + required this.executablePath, + this.rustupHome, + this.cargoHome, + this.logger, + }); + + List? _cachedToolchains; + + Future> installedToolchains() async { + return await _mutex.protect(() async { + _cachedToolchains ??= await _getInstalledToolchains(); + return _cachedToolchains!; + }); + } + + Future getToolchain(String name) async { + return (await installedToolchains()).firstWhereOrNull( + (e) => e.name == name || e.name.startsWith('$name-'), + ); + } + + Future installToolchain(String name) async { + return await _mutex.protect(() async { + _cachedToolchains = null; + await runCommand(['toolchain', 'install', name, '--profile', 'minimal']); + }); + } + + Future> _getInstalledToolchains() async { + String extractToolchainName(String line) { + // ignore (default) after toolchain name + final parts = line.split(' '); + return parts[0]; + } + + final res = await runCommand(['toolchain', 'list']); + + // To list all non-custom toolchains, we need to filter out lines that + // don't start with "stable", "beta", or "nightly". + Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) + .map(extractToolchainName) + .toList(growable: true); + + return lines + .map( + (name) => RustupToolchain( + name: name, + rustup: this, + ), + ) + .toList(growable: true); + } + + Future uninstall() { + return runCommand(['self', 'uninstall', '-y']); + } + + Future runCommand( + List arguments, { + Map? environment, + Logger? logger, + }) { + return common.runCommand( + executablePath, + arguments, + environment: { + if (environment != null) ...environment, + if (rustupHome != null) 'RUSTUP_HOME': rustupHome!, + if (cargoHome != null) 'CARGO_HOME': cargoHome!, + }, + logger: logger ?? this.logger, + ); + } + + /// Returns the path to the `rustup` executable, or `null` if it is not found. + static String? _findExecutablePath() { + final envPath = Platform.environment['PATH']; + final envPathSeparator = Platform.isWindows ? ';' : ':'; + final home = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + final paths = [ + if (home != null) path.join(home, '.cargo', 'bin'), + if (envPath != null) ...envPath.split(envPathSeparator), + ]; + for (final p in paths) { + final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; + final rustupPath = path.join(p, rustup); + if (File(rustupPath).existsSync()) { + return rustupPath; + } + } + return null; + } + + final String executablePath; + final String? rustupHome; + final String? cargoHome; + final Logger? logger; +} + +class RustTarget { + RustTarget({ + required this.architecture, + required this.os, + required this.triple, + this.iosSdk, + }); + + final Architecture architecture; + final OS os; + final IOSSdk? iosSdk; + final String triple; + + static RustTarget? fromTriple(String triple) { + return _targets.firstWhereOrNull((e) => e.triple == triple); + } + + static RustTarget? from({ + required Architecture architecture, + required OS os, + required IOSSdk? iosSdk, + }) { + return _targets.firstWhereOrNull( + (e) => e.architecture == architecture && e.os == os && e.iosSdk == iosSdk, + ); + } + + @override + bool operator ==(Object other) { + if (other is RustTarget) { + return os == other.os && + architecture == other.architecture && + iosSdk == other.iosSdk && + triple == other.triple; + } + return false; + } + + @override + int get hashCode => Object.hash( + os, + architecture, + iosSdk, + triple, + ); + + @override + String toString() => triple; + + static List get allTargets => _targets; + + static final _targets = [ + RustTarget( + os: OS.android, + architecture: Architecture.arm, + triple: 'arm-linux-androideabi', + ), + RustTarget( + os: OS.android, + architecture: Architecture.arm64, + triple: 'aarch64-linux-android', + ), + RustTarget( + os: OS.android, + architecture: Architecture.ia32, + triple: 'i686-linux-android', + ), + RustTarget( + os: OS.android, + architecture: Architecture.x64, + triple: 'x86_64-linux-android', + ), + RustTarget( + os: OS.fuchsia, + architecture: Architecture.arm64, + triple: 'aarch64-unknown-fuchsia', + ), + RustTarget( + os: OS.fuchsia, + architecture: Architecture.x64, + triple: 'x86_64-unknown-fuchsia', + ), + RustTarget( + os: OS.iOS, + iosSdk: IOSSdk.iPhoneOS, + architecture: Architecture.arm64, + triple: 'aarch64-apple-ios', + ), + RustTarget( + os: OS.iOS, + iosSdk: IOSSdk.iPhoneSimulator, + architecture: Architecture.x64, + triple: 'x86_64-apple-ios', + ), + RustTarget( + os: OS.iOS, + iosSdk: IOSSdk.iPhoneSimulator, + architecture: Architecture.arm64, + triple: 'aarch64-apple-ios-sim', + ), + RustTarget( + os: OS.macOS, + architecture: Architecture.arm64, + triple: 'aarch64-apple-darwin', + ), + RustTarget( + os: OS.macOS, + architecture: Architecture.x64, + triple: 'x86_64-apple-darwin', + ), + RustTarget( + os: OS.windows, + architecture: Architecture.arm64, + triple: 'aarch64-pc-windows-msvc', + ), + RustTarget( + os: OS.windows, + architecture: Architecture.ia32, + triple: 'i686-pc-windows-msvc', + ), + RustTarget( + os: OS.windows, + architecture: Architecture.x64, + triple: 'x86_64-pc-windows-msvc', + ), + RustTarget( + os: OS.linux, + architecture: Architecture.arm64, + triple: 'aarch64-unknown-linux-gnu', + ), + RustTarget( + os: OS.linux, + architecture: Architecture.x64, + triple: 'x86_64-unknown-linux-gnu', + ), + ]; +} + +class RustupToolchain { + RustupToolchain({ + required this.name, + required this.rustup, + }); + + final String name; + final Rustup rustup; + + Future rustVersion() async { + final res = await rustup.runCommand(['run', name, 'rustc', '--version']); + final versionString = res.stdout.toString().split(' ')[1]; + return Version.parse(versionString); + } + + Future> installedTargets() async { + return await _mutex.protect(() async { + _cachedTargets ??= await _getInstalledTargets(); + return _cachedTargets!; + }); + } + + Future installTarget(RustTarget target) async { + return await _mutex.protect(() async { + _cachedTargets = null; + await rustup + .runCommand(['target', 'add', target.triple, '--toolchain', name]); + }); + } + + Future> _getInstalledTargets() async { + final res = await rustup.runCommand([ + 'target', + 'list', + '--toolchain', + name, + '--installed', + ]); + final lines = res.stdout.toString().split('\n').where((e) => e.isNotEmpty); + return lines + .map((e) => RustTarget.fromTriple(e)) + .whereNotNull() + .toList(growable: false); + } + + List? _cachedTargets; +} diff --git a/rustup/pubspec.yaml b/rustup/pubspec.yaml new file mode 100644 index 0000000..47551d7 --- /dev/null +++ b/rustup/pubspec.yaml @@ -0,0 +1,21 @@ +name: rustup +description: "Dart bindings for the rustup CLI tool." +version: 0.1.0-dev.2 +homepage: https://github.com/irondash/native_toolchain_rust.git + +environment: + sdk: ">=3.4.0-190.0.dev <4.0.0" + +dependencies: + native_toolchain_rust_common: ^0.1.0-dev.2 + process: ^5.0.2 + pub_semver: ^2.1.4 + native_assets_cli: ^0.5.4 + http: ^1.2.1 + logging: ^1.2.0 + path: ^1.9.0 + collection: ^1.18.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.21.0 diff --git a/rustup/test/installer_test.dart b/rustup/test/installer_test.dart new file mode 100644 index 0000000..5452869 --- /dev/null +++ b/rustup/test/installer_test.dart @@ -0,0 +1,76 @@ +import 'dart:io'; + +import 'package:rustup/rustup.dart'; +import 'package:test/test.dart'; +import 'package:path/path.dart' as path; + +void main() { + test('installer works', () async { + final tempDir = Directory.systemTemp.createTempSync('rustup'); + tempDir.createSync(recursive: true); + final installer = await RustupInstaller.create(); + final cargoHome = path.join(tempDir.path, 'cargo'); + final rustupHome = path.join(tempDir.path, 'rustup'); + try { + await installer.install( + modifyPath: false, + cargoHome: cargoHome, + rustupHome: rustupHome, + ); + } finally { + int attempt = 0; + while (true) { + try { + await installer.dispose(); + break; + } catch (e) { + if (attempt > 5) { + rethrow; + } + attempt++; + // Windows being windows. + print('Failed to clean installer temp dir: $e'); + await Future.delayed(const Duration(seconds: 1)); + } + } + } + expect(tempDir.listSync(), isNotEmpty); + final rustup = Rustup( + executablePath: path.join( + tempDir.path, + 'cargo', + 'bin', + Platform.isWindows ? 'rustup.exe' : 'rustup', + ), + cargoHome: cargoHome, + rustupHome: rustupHome, + ); + print('Install path $tempDir'); + var toolchains = await rustup.installedToolchains(); + expect(toolchains, isEmpty); + await rustup.installToolchain('stable'); + toolchains = await rustup.installedToolchains(); + expect(toolchains, isNotEmpty); + expect(toolchains.first.name.startsWith('stable-'), isTrue); + await rustup.uninstall(); + if (!Platform.isWindows) { + // Rustup on windows doesn't seem to uninstall completely. + expect(tempDir.listSync(), isEmpty); + } + int attempt = 0; + while (true) { + try { + tempDir.deleteSync(recursive: true); + break; + } catch (e) { + if (attempt > 5) { + rethrow; + } + attempt++; + // Windows being windows again. + print('Failed to clean temp dir: $e'); + await Future.delayed(const Duration(seconds: 1)); + } + } + }, timeout: Timeout.none); +} diff --git a/rustup/test/rustup_test.dart b/rustup/test/rustup_test.dart new file mode 100644 index 0000000..1e1c47b --- /dev/null +++ b/rustup/test/rustup_test.dart @@ -0,0 +1,124 @@ +import 'package:native_toolchain_rust_common/native_toolchain_rust_common.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:rustup/src/rustup.dart'; +import 'package:test/test.dart'; + +void main() { + test('rustup with no toolchains', () async { + final processManager = FakeProcessManager.list([ + FakeCommand( + command: ['rustup', 'toolchain', 'list'], + stdout: 'no installed toolchains\n', + ), + FakeCommand( + command: [ + 'rustup', + 'toolchain', + 'install', + 'stable', + '--profile', + 'minimal' + ], + ), + FakeCommand( + command: ['rustup', 'toolchain', 'list'], + stdout: 'stable-aarch64-apple-darwin\n', + ), + FakeCommand( + command: [ + 'rustup', + 'target', + 'list', + '--toolchain', + 'stable-aarch64-apple-darwin', + '--installed' + ], + stdout: 'x86_64-unknown-linux-gnu\nx86_64-apple-darwin\n', + ), + ]); + await withProcessManager(processManager, () async { + final rustup = Rustup(executablePath: 'rustup'); + + expect(await rustup.installedToolchains(), []); + await rustup.installToolchain('stable'); + + expect((await rustup.installedToolchains()).length, 1); + final toolchain = (await rustup.installedToolchains()).first; + expect(toolchain.name, 'stable-aarch64-apple-darwin'); + + final targets = await toolchain.installedTargets(); + expect(targets, [ + RustTarget.fromTriple('x86_64-unknown-linux-gnu')!, + RustTarget.fromTriple('x86_64-apple-darwin')!, + ]); + expect(processManager, hasNoRemainingExpectations); + }); + }); + + test('rustup with esp toolchain', () async { + final processManager = FakeProcessManager.list([ + FakeCommand( + command: [ + 'rustup', + 'toolchain', + 'list', + ], + stdout: 'stable-aarch64-apple-darwin (default)\n' + 'nightly-aarch64-apple-darwin\n' + 'esp\n', + ), + ]); + await withProcessManager(processManager, () async { + final rustup = Rustup(executablePath: 'rustup'); + final toolchains = await rustup.installedToolchains(); + expect(toolchains.length, 2); + expect(toolchains[0].name, 'stable-aarch64-apple-darwin'); + expect(toolchains[1].name, 'nightly-aarch64-apple-darwin'); + expect(processManager, hasNoRemainingExpectations); + }); + }); + + test('toolchain version', () async { + final processManager = FakeProcessManager.list([ + FakeCommand( + command: ['rustup', 'toolchain', 'list'], + stdout: 'stable-aarch64-apple-darwin (default)\n' + 'nightly-aarch64-apple-darwin\n', + ), + FakeCommand( + command: [ + 'rustup', + 'run', + 'stable-aarch64-apple-darwin', + 'rustc', + '--version' + ], + stdout: 'rustc 1.75.0 (82e1608df 2023-12-21)', + ), + FakeCommand( + command: [ + 'rustup', + 'run', + 'nightly-aarch64-apple-darwin', + 'rustc', + '--version' + ], + stdout: 'rustc 1.77.0-nightly (11f32b73e 2024-01-31)', + ), + ]); + await withProcessManager(processManager, () async { + final rustup = Rustup(executablePath: 'rustup'); + { + final toolchain = (await rustup.getToolchain('stable'))!; + final version = await toolchain.rustVersion(); + expect(version, Version(1, 75, 0)); + } + { + final toolchain = (await rustup.getToolchain('nightly'))!; + final version = await toolchain.rustVersion(); + expect(version, Version(1, 77, 0, pre: 'nightly')); + } + expect(processManager, hasNoRemainingExpectations); + }); + }); +}