├── .github ├── FUNDING.yml └── workflows │ ├── codecov.yml │ └── native_executable.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── bin └── kradle.dart ├── codecov.yml ├── dart_test.yaml ├── dartdoc_options.yaml ├── doc ├── consumer.md ├── gradle.md ├── producer.md └── tasks.md ├── example └── README.md ├── lib ├── klutter.dart └── src │ ├── cli │ ├── cli.dart │ ├── context.dart │ ├── flutter.dart │ ├── option.dart │ ├── runner.dart │ ├── task.dart │ ├── task_add.dart │ ├── task_build.dart │ ├── task_clean_cache.dart │ ├── task_get_flutter.dart │ ├── task_project_create.dart │ ├── task_project_init.dart │ ├── task_result.dart │ └── task_service.dart │ ├── common │ ├── common.dart │ ├── config.dart │ ├── environment.dart │ ├── exception.dart │ ├── executor.dart │ ├── project.dart │ └── utilities.dart │ ├── consumer │ ├── android.dart │ ├── consumer.dart │ └── ios.dart │ └── producer │ ├── android.dart │ ├── gradle.dart │ ├── ios.dart │ ├── kradle.dart │ ├── platform.dart │ ├── producer.dart │ ├── project.dart │ └── resource.dart ├── logo_animated.gif ├── pubspec.yaml ├── resources.tar.gz ├── resources.zip ├── resources ├── create_tar_resources.sh ├── gradle-wrapper.jar ├── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── kradle.env └── kradle.yaml ├── rules.yaml └── test └── src ├── cli ├── cli_test.dart ├── context_test.dart ├── option_test.dart ├── runner_test.dart ├── task_add_test.dart ├── task_get_flutter_test.dart ├── task_name_test.dart ├── task_project_build_test.dart ├── task_project_clean_cache_test.dart ├── task_project_create_test.dart ├── task_project_init_test.dart ├── task_service_test.dart └── task_test.dart ├── common ├── config_test.dart ├── exception_test.dart ├── executor_test.dart ├── project_kradle_test.dart ├── project_test.dart └── utilities_test.dart ├── consumer └── android_test.dart ├── producer ├── android_test.dart ├── gradle_test.dart ├── ios_test.dart ├── kradle_test.dart ├── platform_test.dart └── project_test.dart └── systemtest ├── e2e_test.dart └── e2e_test_with_config.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [buijs-dev] -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Publish Dart to Codecov 2 | on: [push] 3 | jobs: 4 | build: 5 | strategy: 6 | matrix: 7 | os: [macos-latest, windows-latest, ubuntu-latest] 8 | runs-on: ${{ matrix.os }} 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-java@v2 12 | with: 13 | distribution: 'zulu' 14 | java-version: '17' 15 | - uses: subosito/flutter-action@v2 16 | with: 17 | flutter-version: '3.10.6' 18 | channel: 'stable' 19 | 20 | # Get dependencies 21 | - name: Install dependencies 22 | run: dart pub get 23 | 24 | # Run all tests with coverage 25 | - name: Run tests with coverage 26 | run: dart run test --coverage="coverage" 27 | 28 | # Convert to LCOV 29 | - name: Convert coverage to LCOV 30 | run: dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --packages=.dart_tool/package_config.json --report-on=lib 31 | 32 | # Upload coverage data 33 | - name: Upload coverage to Codecov 34 | uses: codecov/codecov-action@v3 35 | env: 36 | CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} 37 | with: 38 | file: coverage.lcov -------------------------------------------------------------------------------- /.github/workflows/native_executable.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish native executable kradlew 2 | on: [push] 3 | jobs: 4 | build: 5 | strategy: 6 | matrix: 7 | os: [macos-latest, windows-latest, ubuntu-latest] 8 | runs-on: ${{ matrix.os }} 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-java@v2 12 | with: 13 | distribution: 'zulu' 14 | java-version: '17' 15 | - uses: subosito/flutter-action@v2 16 | with: 17 | flutter-version: '3.10.6' 18 | channel: 'stable' 19 | - name: Install dependencies 20 | run: dart pub get 21 | - name: Build executable 22 | run: dart compile exe bin/kradle.dart -o kradle 23 | - uses: actions/upload-artifact@v4 24 | with: 25 | name: kradle-${{ matrix.os }} 26 | path: kradle 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 25 | /pubspec.lock 26 | **/doc/api/ 27 | .dart_tool/ 28 | .packages 29 | build/ 30 | /format.sh 31 | /checklist.txt 32 | /coverage/ 33 | /pana.sh 34 | /resimport.sh 35 | /dartdoc.sh 36 | /example/ridiculous_awesome/ 37 | /kradlew 38 | /build_dist.sh 39 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.2 2 | * Use Klutter Gradle v2024.1.3.beta. 3 | 4 | ## 3.0.1 5 | * Use Klutter Gradle v2024.1.2.beta. 6 | 7 | ## 3.0.0 8 | * Use klutter_ui 1.1.0. 9 | * Use Klutter Gradle v2024.1.1.beta with support for protobuf. 10 | * Set iOS to 13.0 in Podfile and podspec. 11 | * Move post-build tasks in root/platform build.gradle.kts to gradle plugin. 12 | * Add kradle script (which replaces producer and consumer). 13 | * Add interactive cli mode. 14 | * Add project create and build tasks. 15 | * Remove producer and consumer scripts. 16 | * Refactor library to enable generating a native executable. 17 | 18 | ## 2.0.0 19 | * Uses AGP 8.0.2 in projects. 20 | * Removed AndroidManifest.xml references. 21 | * Add code generation for example/app/build.gradle to control dependency versions used. 22 | * Uses kradle.yaml instead of klutter.yaml. 23 | * Bump SDK constraints to >=2.17.6 <4.0.0. 24 | * Uses Klutter Gradle v2023.3.1.beta. 25 | * Uses klutter_ui 1.0.1. 26 | * Removed deprecated TaskName install. 27 | * Add support for Linux. 28 | 29 | ## 1.0.0 30 | * Uses Klutter Gradle v2023.1.2.beta. 31 | * Uses klutter_ui 1.0.0. 32 | * Add support for windows development ([ticket](https://github.com/buijs-dev/klutter-dart/issues/3)). 33 | 34 | ## 0.3.0 35 | * Uses Klutter Gradle v2023.1.1.beta. 36 | * Removed producer install tasks because Klutter Gradle v2023.1.1.beta does the installation during gradle build. 37 | * Remove widgets in order to scope klutter-dart to dev_dependency (widgets are now found in [klutter-dart-ui](https://github.com/buijs-dev/klutter-dart-ui). 38 | * Embedded gradle-wrapper bumped to version 7.2. 39 | * Removed consumer init ios task because no longer required. 40 | * Consumer add uses $root variable in local paths in .klutter-plugins file. 41 | * Add logic to klutter_plugin_loaders.gradle.kts to replace $root variable with local path (backwards compatible). 42 | 43 | ## 0.2.4 44 | * Documentation update to point-out the Android Studio and Intellij IDE plugins. 45 | 46 | ## 0.2.3 47 | * Uses Klutter Gradle v2022.r6-9.alpha. 48 | 49 | ## 0.2.1 50 | * Changed gradle plugin id to dev.buijs.klutter 51 | 52 | ## 0.2.0 53 | * Uses Klutter Gradle v2022.r6-8.alpha. 54 | * New project template uses Klutter DSL to apply Klutter dependencies. 55 | * Flutter generated files are removed from lib folder after klutter init. 56 | * Moved task klutterInstallPlatform from generated build.gradle.kts to Gradle plugin. 57 | * Renamed task klutterInstallPlatfrom to klutterBuild. 58 | * Moved task klutterCopyAarFile from generated build.gradle.kts to Gradle plugin. 59 | * Moved task klutterIosFramework from generated build.gradle.kts to Gradle plugin. 60 | * Changed platform build.gradle.kts to create an XCFramework instead of fat framework for iOS. 61 | * [Bugfix](https://github.com/buijs-dev/klutter/issues/4) App does not work on Mac M1. 62 | 63 | ## 0.1.3 64 | * Uses Klutter Gradle v2022.r6-7.alpha. 65 | 66 | ## 0.1.2 67 | * Uses Klutter Gradle v2022.r6-6.alpha. 68 | * Added adapter library with improved AdapterResponse class. 69 | * As of 0.1.2 Klutter is required as dependency instead of dev_dependency. 70 | 71 | ## 0.1.1 72 | * Producer Install task no longer depends on Init task. 73 | * Formatting fixes in documentation. 74 | 75 | ## 0.1.0 76 | * Uses Klutter Gradle v2022.r6.alpha. 77 | * Initial version with iOS and Android support. 78 | * Contains the tasks: 79 | * klutter:consumer init 80 | * klutter:consumer init=android 81 | * klutter:consumer init=android,ios 82 | * klutter:producer init 83 | * klutter:producer install=platform 84 | * klutter:producer install=library -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 - 2024 Buijs Software 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/badge/Buijs-Software-blue)](https://pub.dev/publishers/buijs.dev/packages) 2 | [![GitHub license](https://img.shields.io/github/license/buijs-dev/klutter-dart?color=black&logoColor=black)](https://github.com/buijs-dev/klutter-dart/blob/main/LICENSE) 3 | [![pub](https://img.shields.io/pub/v/klutter)](https://pub.dev/packages/klutter) 4 | [![codecov](https://img.shields.io/codecov/c/github/buijs-dev/klutter-dart?logo=codecov)](https://codecov.io/gh/buijs-dev/klutter-dart) 5 | [![CodeScene Code Health](https://codescene.io/projects/27237/status-badges/code-health)](https://codescene.io/projects/27237) 6 | 7 | 8 |
9 | 10 | buijs software logo 11 | 12 | The Klutter Framework makes it possible to write a Flutter plugin for both Android 13 | and iOS using [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). 14 | Instead of writing platform specific code twice in 2 languages (Swift + Kotlin), 15 | it can be written once in Kotlin and used as a Flutter plugin. 16 | 17 | # Features 18 | 19 | Use this plugin if you want to: 20 | 21 | * Write platform-specific code only once for both Android and IOS in Kotlin. 22 | * Use Kotlin Multiplatform libraries in your Flutter app. 23 | * Depend on other plugins made with Klutter. 24 | * Auto-generate Dart code for your (native) Platform library. 25 | 26 | # Getting started 27 | 1. [Installation](#Installation) 28 | 2. [Using plugins](#Usage) 29 | 3. [Creating plugins](#Creation) 30 | 4. [FAQ](#Faq) 31 | 32 | - Start a new project with the 33 | - [Intellij](https://buijs.dev/klutter-3/) plugin 34 | - [Android Studio](https://buijs.dev/klutter-4/) plugin 35 | - [Kradle](https://buijs.dev/kradle-1/) cli tool 36 | - For a step-by-step guide (doing everything manually), see the battery app with Klutter [tutorial](https://buijs.dev/klutter-2/). 37 | 38 | Using the IDE plugins or interactive kradle tool are the 39 | preferred options to create projects, as opposed 40 | to doing everything manually. You can install 41 | the kradle tool from git or pub: 42 | 43 | ```shell 44 | ## Get from pub 45 | dart pub global activate klutter 46 | 47 | ## Get from git 48 | dart pub global activate --source git https://github.com/buijs-dev/klutter-dart.git 49 | 50 | ## Use it globally 51 | dart pub global run klutter:kradle 52 | ``` 53 | 54 | A native kradle executable is added to the project workspace, 55 | when creating a new project using an IDE plugin. 56 | 57 | See the kradle [tutorial](https://buijs.dev/kradle-1/) for usage instructions. 58 | 59 | # Installation 60 | What's the point?
61 | Plugins build with the Klutter Framework work slightly different from regular plugins. 62 | The Klutter dependency is a requirement for both using and creating plugins with Klutter. 63 | 64 | Steps:
65 | Add the Klutter library to dependencies in the pubspec.yaml: 66 | 67 | ```yaml 68 | dev_dependencies: 69 | klutter: ^3.0.1 70 | ``` 71 | 72 | Then run: 73 | 74 | ```shell 75 | flutter pub get 76 | ``` 77 | 78 | # Usage 79 | What's the point?
80 | Plugins build with the Klutter Framework work slightly different from regular plugins. 81 | The following tasks help Flutter to locate Klutter plugins 82 | and ensure compatibility between Flutter Android/IOS configuration and Klutter plugin Android/IOS configuration. 83 | 84 | Steps:
85 | 1. Installation. 86 | 2. Initialization. 87 | 3. Add dependencies. 88 | 89 | Install Klutter as dependency as described [here](#Installation). 90 | 91 | Initialize Klutter in your project by running: 92 | 93 | ```shell 94 | dart run klutter:kradle init 95 | ``` 96 | 97 | The init task will set up Klutter for both Android and iOS. 98 | Klutter plugins can be added by running the add command. 99 | 100 | Example:
Add the library 'awesome_plugin' to your project: 101 | 102 | ```shell 103 | dart run klutter:kradle add lib=awesome_plugin 104 | ``` 105 | 106 | Background
107 | The consumer init task will configure your Flutter project in: 108 | 1. [IOS](#ios) 109 | 2. [Android](#android) 110 | 111 | IOS
112 | The Podfile has to be editted to be able to run the app on an iPhone simulator. 113 | Klutter will look for the following code block in the Podfile: 114 | 115 | ``` 116 | post_install do |installer| 117 | installer.pods_project.targets.each do |target| 118 | flutter_additional_ios_build_settings(target) 119 | end 120 | end 121 | ``` 122 | 123 | Then it will be updated to the following code: 124 | 125 | ``` 126 | post_install do |installer| 127 | installer.pods_project.targets.each do |target| 128 | flutter_additional_ios_build_settings(target) 129 | target.build_configurations.each do |bc| 130 | bc.build_settings['ARCHS[sdk=iphonesimulator*]'] = `uname -m` 131 | end 132 | end 133 | end 134 | ``` 135 | 136 | 137 | Android
138 | The consumer init task will do the following for Android in your Flutter project: 139 | 1. Create a .klutter-plugins file in the root folder. 140 | 2. Create a new Gradle file in the flutter/packages/flutter_tools/gradle. 141 | 3. Update the android/settings.gradle file to apply the newly generated Gradle file. 142 | 4. Update the min/target SDK versions to 24/33 in the android/app/build.gradle file. 143 | 5. Update the Android Gradle Plugin to 8.0.2 and gradle-wrapper to Gradle 8+. 144 | 145 | The .klutter-plugins file will register all Klutter made plugins used in your project. 146 | The created Gradle file in the flutter_tools manages the plugins 147 | and enables them to be found by the Flutter project. 148 | 149 | The task klutter:add registers a Klutter plugin in the .klutter-plugins file. 150 | This is then used by the Android Gradle file to find the plugin location 151 | and add the generated artifacts to your build. 152 | 153 | # Creation 154 | What's the point?
155 | The starting point of a Klutter plugins is a regular Flutter plugin project. 156 | The following steps describe how to create a Flutter plugin project and initialize Klutter in it. 157 | 158 | Steps:
159 | 1. Create Flutter plugin project. 160 | 2. [Installation](#Installation). 161 | 3. Initialization. 162 | 4. Build Platform module and generate Dart code. 163 | 5. Verify your plugin. 164 | 165 | Run the following to create a new Flutter plugin, 166 | substituting 'org.example' with your organisation name 167 | and 'plugin_name' with your plugin name: 168 | 169 | ```shell 170 | flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin -i swift plugin_name 171 | ``` 172 | 173 | Install the Klutter Framework as dependency and then run: 174 | 175 | ```shell 176 | dart run kradle:init 177 | ``` 178 | 179 | Build the platform module by running the following in the root folder (takes a few minutes): 180 | 181 | ```shell 182 | dart run klutter:kradle build 183 | ``` 184 | 185 | Alternatively use gradle directly with the following command: 186 | 187 | ```shell 188 | ./gradlew clean build -p "platform" 189 | ``` 190 | 191 | Now test the plugin by following the steps outlined [here](#Usage) in the root/example project. 192 | When done you can run the example project from the root/example/lib folder and see your first plugin in action! 193 | 194 | # Faq 195 | 1. [App won't start on...](#App%20won't%20start) 196 | 2. [Build fails](#build-fails) 197 | 198 | ## App won't start 199 | Make sure you have followed all the following steps: 200 | - flutter create --org --template=plugin --platforms=android,ios -a kotlin -i swift. 201 | - [klutter](https://pub.dev/packages/klutter) is added to the dependencies in your pubspec.yaml 202 | (both the plugin and plugin/example for testing). 203 | - do flutter pub get in both root and root/example folder. 204 | - do flutter pub run klutter:kradle init in the root folder. 205 | - do ./gradlew clean build -p "platform" in the root folder. 206 | - do flutter pub run klutter:kradle init in the root/example folder. 207 | - do flutter pub run klutter:kradle add lib= in the root/example folder. 208 | 209 | ### For Android emulator: 210 | There should be a .klutter-plugins file in the root/example folder containing an entry for your plugin. 211 | If not then do flutter pub run klutter:consumer add= in the root/example folder again. 212 | 213 | There should be a platform.aar file in the root/android/klutter folder. 214 | If not then do ./gradlew clean build -p "platform" from the root folder. 215 | 216 | ### For iOS simulator: 217 | There should be a Platform.xcframework folder in root/ios/Klutter. 218 | If not then do ./gradlew clean build -p "platform" from the root folder. 219 | 220 | If there's an error message saying unable to find plugin or similar then run pod update 221 | (or for Mac M1 users you might have to do: arch -x86_64 pod install) in the root/example/ios 222 | folder. 223 | 224 | If there's an error message saying something similiar to '...example/ios/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh: Permission denied' 225 | then try one of: 226 | - delete the Podfile.lock and run pod install in root/example/ios folder. 227 | - run pod deintegrate and then pod install in root/example/ios folder. 228 | 229 | ## Build fails 230 | 1. [Java toolchain error](#java-toolchain-error) 231 | 232 | ### Java toolchain error 233 | When you get an error like below, indicating no compatible Java version is detected, make sure you 234 | have the same Java version installed as required by the klutter project. 235 | 236 | ```shell 237 | * What went wrong: 238 | Could not determine the dependencies of task ':klutter:hello_world:compileDebugKotlinAndroid'. 239 | > No matching toolchains found for requested specification: {languageVersion=17, vendor=any, implementation=vendor-specific}. 240 | > No locally installed toolchains match ... 241 | ``` 242 | 243 | You might also have to add the following plugin to the settings.gradle(.kts) files: 244 | 245 | ```kotlin 246 | plugins { 247 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" 248 | } 249 | ``` -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Based this of the provider package: https://github.com/rrousselGit/provider 2 | # Not 100% similar! Different personal preferences. 3 | include: rules.yaml 4 | analyzer: 5 | exclude: 6 | - "**/*.g.dart" 7 | strong-mode: 8 | strict-casts: false 9 | strict-raw-types: false 10 | errors: 11 | # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. 12 | # We explicitly enabled even conflicting rules and are fixing the conflict in this file 13 | included_file_warning: ignore 14 | linter: 15 | rules: 16 | # Personal preference. I love it ;) 17 | cascade_invocations: true 18 | 19 | # Double quotes are in my system ;) 20 | prefer_single_quotes: false 21 | 22 | # Conflicts with `omit_local_variable_types` and other rules. 23 | # As per Dart guidelines, we want to avoid unnecessary types to make the code 24 | # more readable. 25 | # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables 26 | always_specify_types: false 27 | 28 | # Incompatible with `prefer_final_locals` 29 | # Having immutable local variables makes larger functions more predictible 30 | # so we will use `prefer_final_locals` instead. 31 | unnecessary_final: false 32 | 33 | # Not quite suitable for Flutter, which may have a `build` method with a single 34 | # return, but that return is still complex enough that a "body" is worth it. 35 | prefer_expression_function_bodies: false 36 | 37 | # Conflicts with the convention used by flutter, which puts `Key key` 38 | # and `@required Widget child` last. 39 | always_put_required_named_parameters_first: false 40 | 41 | # `as` is not that bad (especially with the upcoming non-nullable types). 42 | # Explicit exceptions is better than implicit exceptions. 43 | avoid_as: false 44 | 45 | # This project doesn't use Flutter-style todos 46 | flutter_style_todos: false 47 | 48 | # There are situations where we voluntarily want to catch everything, 49 | # especially as a library. 50 | avoid_catches_without_on_clauses: false 51 | 52 | # Boring as it sometimes force a line of 81 characters to be split in two. 53 | # As long as we try to respect that 80 characters limit, going slightly 54 | # above is fine. 55 | lines_longer_than_80_chars: false 56 | 57 | # Conflicts with disabling `implicit-dynamic` 58 | avoid_annotating_with_dynamic: false 59 | 60 | # conflicts with `prefer_relative_imports` 61 | always_use_package_imports: false 62 | 63 | # Disabled for now until we have NNBD as it otherwise conflicts with `missing_return` 64 | no_default_cases: false 65 | 66 | # False positive, null checks don't need a message 67 | prefer_asserts_with_message: false 68 | 69 | # Cumbersome with `context.select` 70 | avoid_types_on_closure_parameters: false 71 | 72 | # Too many false positive (builders) 73 | diagnostic_describe_all_properties: false 74 | 75 | # false positives (setter-like functions) 76 | avoid_positional_boolean_parameters: false 77 | 78 | # Does not apply to providers 79 | prefer_const_constructors_in_immutables: false -------------------------------------------------------------------------------- /bin/kradle.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // ignore_for_file: avoid_print 22 | 23 | import "dart:io"; 24 | 25 | import "package:interact/interact.dart" as interact; 26 | import "package:interact/interact.dart"; 27 | import "package:klutter/klutter.dart"; 28 | 29 | /// Run kradle tasks. 30 | Future main(List args) async { 31 | print(""" 32 | ════════════════════════════════════════════ 33 | KRADLE (v$klutterPubVersion) 34 | ════════════════════════════════════════════ 35 | """ 36 | .ok); 37 | 38 | if (args.isNotEmpty) { 39 | print(await run(args)); 40 | return; 41 | } else { 42 | await interactiveMode(); 43 | } 44 | } 45 | 46 | const addLibrary = "add library"; 47 | const getFlutter = "get flutter"; 48 | const cleanCache = "clean cache"; 49 | const projectBuild = "project build"; 50 | const projectCreate = "project create"; 51 | const projectInit = "project init"; 52 | const quit = "quit"; 53 | final tasks = [ 54 | addLibrary, 55 | getFlutter, 56 | cleanCache, 57 | projectBuild, 58 | projectCreate, 59 | projectInit, 60 | quit, 61 | ]; 62 | 63 | Future interactiveMode() async { 64 | final prompt = Select(prompt: "choose task", options: tasks).interact(); 65 | final selection = tasks[prompt]; 66 | if (selection == addLibrary) { 67 | final lib = interact.Input(prompt: "library name").interact(); 68 | await runTaskWithSpinner(TaskName.add, {TaskOption.lib: lib}); 69 | } else if (selection == getFlutter) { 70 | final version = askForFlutterVersion(); 71 | final overwrite = askConfirmation("overwrite existing distribution?"); 72 | await runTaskWithSpinner(TaskName.get, { 73 | TaskOption.flutter: version, 74 | TaskOption.overwrite: "$overwrite", 75 | }); 76 | } else if (selection == cleanCache) { 77 | await runTaskWithSpinner(TaskName.clean, {}); 78 | } else if (selection == projectBuild) { 79 | await runTaskWithSpinner(TaskName.build, {}); 80 | } else if (selection == projectCreate) { 81 | final pluginName = askForUserInputOrDefault(const PluginNameOption()); 82 | final groupName = askForUserInputOrDefault(const GroupNameOption()); 83 | final flutterVersion = askForFlutterVersion(); 84 | final klutterGradle = 85 | askForUserInputOrDefault(const KlutterGradleVersionOption()); 86 | final klutterPub = askForKlutterPubVersion(); 87 | final klutteruiPub = askForKlutteruiPubVersion(); 88 | final squint = askForSquintPubVersion(); 89 | final workingDirectory = 90 | askConfirmation("create in current directory?", true) 91 | ? Directory.current.absolutePath 92 | : interact.Input(prompt: "working directory").interact(); 93 | await runTaskWithSpinner(TaskName.create, { 94 | TaskOption.name: pluginName, 95 | TaskOption.group: groupName, 96 | TaskOption.flutter: flutterVersion, 97 | TaskOption.root: workingDirectory, 98 | TaskOption.klutter: klutterPub, 99 | TaskOption.klutterui: klutteruiPub, 100 | TaskOption.bom: klutterGradle, 101 | TaskOption.squint: squint, 102 | }); 103 | } else if (selection == projectInit) { 104 | final gradleVersion = 105 | askForUserInputOrDefault(const KlutterGradleVersionOption()); 106 | final flutterVersion = askForFlutterVersion(); 107 | await runTaskWithSpinner(TaskName.init, { 108 | TaskOption.flutter: flutterVersion, 109 | TaskOption.bom: gradleVersion, 110 | }); 111 | } else if (selection == quit) { 112 | return; 113 | } else { 114 | print("oops... something went wrong"); 115 | } 116 | 117 | await interactiveMode(); 118 | } 119 | 120 | String askForFlutterVersion() { 121 | final option = FlutterVersionOption(); 122 | final versions = supportedFlutterVersions.values 123 | .toSet() 124 | .map((e) => e.prettyPrint) 125 | .toList(); 126 | final promptVersion = 127 | Select(prompt: option.description, options: versions).interact(); 128 | return versions[promptVersion]; 129 | } 130 | 131 | String askForUserInputOrDefault(UserInputOrDefault option) { 132 | return interact.Input( 133 | prompt: option.description, defaultValue: option.defaultValue) 134 | .interact(); 135 | } 136 | 137 | String askForKlutterPubVersion() { 138 | final sources = ["git", "pub", "local"]; 139 | final prompt = 140 | Select(prompt: "select klutter (dart) source", options: sources) 141 | .interact(); 142 | final source = sources[prompt]; 143 | 144 | if (source == "git") { 145 | return "https://github.com/buijs-dev/klutter-dart.git@develop"; 146 | } 147 | 148 | if (source == "pub") { 149 | return interact.Input( 150 | prompt: "klutter (dart) version", defaultValue: klutterPubVersion) 151 | .interact(); 152 | } 153 | 154 | final path = 155 | interact.Input(prompt: "path to local klutter (dart)").interact(); 156 | return "local@$path"; 157 | } 158 | 159 | String askForKlutteruiPubVersion() { 160 | final sources = ["git", "pub", "local"]; 161 | final prompt = 162 | Select(prompt: "select klutter_ui (dart) source", options: sources) 163 | .interact(); 164 | final source = sources[prompt]; 165 | 166 | if (source == "git") { 167 | return "https://github.com/buijs-dev/klutter-dart-ui.git@develop"; 168 | } 169 | 170 | if (source == "pub") { 171 | return interact.Input( 172 | prompt: "klutter_ui (dart) version", 173 | defaultValue: klutterUIPubVersion) 174 | .interact(); 175 | } 176 | 177 | final path = 178 | interact.Input(prompt: "path to local klutter_ui (dart)").interact(); 179 | return "local@$path"; 180 | } 181 | 182 | String askForSquintPubVersion() { 183 | final sources = ["git", "pub", "local"]; 184 | final prompt = Select(prompt: "select squint (dart) source", options: sources) 185 | .interact(); 186 | final source = sources[prompt]; 187 | 188 | if (source == "git") { 189 | return "https://github.com/buijs-dev/squint.git@develop"; 190 | } 191 | 192 | if (source == "pub") { 193 | return interact.Input( 194 | prompt: "squint_json (dart) version", 195 | defaultValue: squintPubVersion) 196 | .interact(); 197 | } 198 | 199 | final path = 200 | interact.Input(prompt: "path to local squint_json (dart)").interact(); 201 | return "local@$path"; 202 | } 203 | 204 | bool askConfirmation(String prompt, [bool? defaultValue]) => Confirm( 205 | prompt: prompt, 206 | defaultValue: defaultValue ?? false, 207 | waitForNewLine: true, 208 | ).interact(); 209 | 210 | Future runTaskWithSpinner( 211 | TaskName taskName, Map options) async { 212 | final loading = Spinner(icon: "..").interact(); 213 | final args = [taskName.name]; 214 | options.forEach((key, value) { 215 | args.add("${key.name}=$value"); 216 | }); 217 | print(await run(args)); 218 | loading.done(); 219 | } 220 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: #download task, don't want to test it in a UT 2 | - "**/task_get_flutter.dart" -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | # ST (SystemTest) is a long slow test so give it some time 3 | ST: 4 | timeout: 10x 5 | -------------------------------------------------------------------------------- /dartdoc_options.yaml: -------------------------------------------------------------------------------- 1 | dartdoc: 2 | categories: 3 | "consumer": 4 | markdown: doc/consumer.md 5 | name: consumer 6 | "producer": 7 | markdown: doc/producer.md 8 | name: producer 9 | "gradle": 10 | markdown: doc/gradle.md 11 | name: gradle 12 | "tasks": 13 | markdown: doc/tasks.md 14 | name: tasks 15 | categoryOrder: ["consumer", "producer", "gradle", "tasks"] 16 | -------------------------------------------------------------------------------- /doc/consumer.md: -------------------------------------------------------------------------------- 1 | In Klutter terminology a consumer is a project using (consuming) a plugin 2 | that is made with Klutter. 3 | 4 | Klutter plugins contain native artifacts. These are .aar files for Android 5 | and Frameworks for iOS. A standard Flutter project won't be able to find 6 | these artifacts for a Klutter plugin which means they won't work. 7 | 8 | To work with Klutter plugins the following 2 tasks are required: 9 | - kradle init (initialize Klutter in your project) 10 | - kradle add (add a Klutter plugin to your project) -------------------------------------------------------------------------------- /doc/gradle.md: -------------------------------------------------------------------------------- 1 | Gradle is a build tool that works on the JVM. Every Flutter project uses Gradle in some form. 2 | For instance the Flutter deliverables for Android are build with Gradle. Klutter however uses 3 | Gradle to work with Kotlin Multiplatform. 4 | 5 | Kotlin Multiplatform projects can only build with Gradle. 6 | Klutter consists of 2 components to make this possible: 7 | - Dart plugin (this library) 8 | - Gradle plugin 9 | 10 | The Klutter Gradle plugin is applied in the Kotlin Multiplatform project, 11 | e.g. in a producer project this is the root/platform folder. This plugin 12 | roughly does two things: 13 | - Generate method-channel code on Flutter and platform side. 14 | - Build native artifacts for both iOS and Android. 15 | 16 | Gradle needs to be installed to be able to do all this. 17 | Even though Flutter installs gradle wrapper in the android folder, 18 | Klutter adds its own wrapper files to producer project. 19 | 20 | This is done to make sure all Gradle versions within the Klutter project 21 | are aligned and to make the Gradle distributions between consumer 22 | and producer projects independent. 23 | 24 | For more information about how to work with Klutter Multiplatform in Klutter 25 | see [here](https://github.com/buijs-dev/klutter). -------------------------------------------------------------------------------- /doc/producer.md: -------------------------------------------------------------------------------- 1 | In Klutter terminology a producer is a project for creating (producing) 2 | a plugin with Klutter. 3 | 4 | A producer project is basically a Flutter plugin project with an extra 5 | Kotlin Multiplatform module where all (native) platform code will be written. 6 | 7 | The following producer tasks are available: 8 | - kradle create (create new klutter project) 9 | - kradle init (initialize Klutter in an existing flutter project) -------------------------------------------------------------------------------- /doc/tasks.md: -------------------------------------------------------------------------------- 1 | Klutter tasks are used to create, build and manage klutter projects and their dependencies. -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 |
2 | buijs software logo 3 | 4 | - For installation instructions see [here](./../README.md). 5 | - For a step-by-step guide, see the battery app with Klutter [tutorial](https://buijs.dev/klutter-2/). 6 | - For working with Klutter in the Kotlin Multiplatform module see [here](https://github.com/buijs-dev/klutter). -------------------------------------------------------------------------------- /lib/klutter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// The Klutter Framework makes it possible to write a Flutter plugin for both Android 22 | /// and iOS using [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). 23 | /// 24 | /// Instead of writing platform specific code twice in 2 languages (Swift + Kotlin), 25 | /// it can be written once in Kotlin and used as a Flutter plugin. 26 | library klutter; 27 | 28 | export "src/cli/cli.dart"; 29 | export "src/common/common.dart"; 30 | export "src/consumer/consumer.dart"; 31 | export "src/producer/producer.dart"; 32 | -------------------------------------------------------------------------------- /lib/src/cli/cli.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// Library containing all Klutter tasks available through the command line. 22 | /// 23 | /// Domain specific language used throughout this library: 24 | /// - Command: User input as received through the cli. 25 | /// - ScriptName: The name of a script being either consumer or producer. 26 | /// A script has one or more tasks. 27 | /// - TaskName: The name of a task belonging to a script. 28 | /// - Task: The actual task containing all functionality. 29 | /// - Option: Extra user input which configures a task. 30 | /// An option can be optional or required depending on the task. 31 | /// - TaskResult: The result of task execution. 32 | /// A result is either OK or not. 33 | /// If a result is not OK then a message is given describing the problem. 34 | /// 35 | /// Example 36 | /// 37 | /// Given this command: 38 | /// 39 | /// ```shell 40 | /// flutter pub run klutter:consumer init=android 41 | /// ``` 42 | /// 43 | /// Expect the following: 44 | /// - Command: consumer init=android 45 | /// - ScriptName: consumer 46 | /// - TaskName: init 47 | /// - Task: ConsumerInitAndroid class 48 | /// - Option: android 49 | library cli; 50 | 51 | import "../common/common.dart"; 52 | import "context.dart"; 53 | import "task.dart"; 54 | import "task_service.dart"; 55 | 56 | export "flutter.dart"; 57 | export "option.dart"; 58 | export "runner.dart"; 59 | export "task.dart"; 60 | export "task_add.dart"; 61 | export "task_build.dart"; 62 | export "task_clean_cache.dart"; 63 | export "task_get_flutter.dart"; 64 | export "task_project_create.dart"; 65 | export "task_project_init.dart"; 66 | export "task_result.dart"; 67 | export "task_service.dart"; 68 | 69 | /// Main entrypoint for executing Klutter command line tasks. 70 | Future execute(TaskName taskName, Context context, 71 | [TaskService? taskService]) async { 72 | final service = taskService ?? TaskService(); 73 | 74 | /// Retrieve all tasks for the specified command. 75 | /// 76 | /// Set is empty if command is null or task processing failed. 77 | final task = service.toTask(taskName); 78 | 79 | /// When there are no tasks then the user input is incorrect. 80 | /// 81 | /// Stop processing and print list of available tasks. 82 | if (task == null) { 83 | return """ 84 | |KLUTTER: Received invalid command. 85 | | 86 | |${service.displayKradlewHelpText} 87 | """ 88 | .format 89 | .nok; 90 | } 91 | 92 | final t = task.taskName.name; 93 | var o = ""; 94 | 95 | context.taskOptions.forEach((key, value) { 96 | o += " ${key.name}=$value"; 97 | }); 98 | 99 | final result = await task.execute(context); 100 | final msgOk = "KLUTTER: Task '$t$o' finished successful."; 101 | final msgNok = """ 102 | |KLUTTER: ${result.message} 103 | |KLUTTER: Task '$t$o' finished unsuccessfully."""; 104 | if (result.isOk) { 105 | return msgOk.format.ok; 106 | } else { 107 | return msgNok.format.nok; 108 | } 109 | } 110 | 111 | /// Output log message to console. 112 | extension ColoredMessage on String { 113 | /// Log a green colored message. 114 | String get ok => "\x1B[32m$this"; 115 | 116 | /// Log a red colored message. 117 | String get nok => "\x1B[31m$this"; 118 | 119 | /// Default color (mostly whit(e/ish)). 120 | String get boring => "\x1B[49m$this"; 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/cli/context.dart: -------------------------------------------------------------------------------- 1 | import "dart:io"; 2 | 3 | import "../common/common.dart"; 4 | import "task.dart"; 5 | 6 | /// The context in which a command should be executed. 7 | class Context { 8 | /// Create a new [Context] instance. 9 | const Context(this.workingDirectory, this.taskOptions); 10 | 11 | /// The current working directory. 12 | /// 13 | /// The working directory is used as root 14 | /// when running project tasks, when no 15 | /// [TaskOption.root] option is specified. 16 | final Directory workingDirectory; 17 | 18 | /// All user input mapped as [TaskOption]. 19 | final Map taskOptions; 20 | } 21 | 22 | /// Parse user input and return the [Context] or null if input is invalid. 23 | Context? toContextOrNull(Directory workingDirectory, List arguments) { 24 | if (arguments.isEmpty) { 25 | return Context(workingDirectory, {}); 26 | } 27 | 28 | final taskOptions = toTaskOptionsOrNull(arguments); 29 | if (taskOptions == null) { 30 | return null; 31 | } 32 | 33 | return Context(workingDirectory, taskOptions); 34 | } 35 | 36 | /// Copy utilities for [Context] objects. 37 | extension CopyContext on Context { 38 | /// Create a new [Context] instance where existing 39 | /// fields are overwritten with the given data. 40 | Context copyWith({ 41 | Directory? workingDirectory, 42 | Map taskOptions = const {}, 43 | }) { 44 | this.taskOptions.forEach((key, value) { 45 | taskOptions.putIfAbsent(key, () => value); 46 | }); 47 | 48 | return Context(workingDirectory ?? this.workingDirectory, taskOptions); 49 | } 50 | } 51 | 52 | /// Parse the arguments to map of [TaskOption] or null if input is invalid. 53 | Map? toTaskOptionsOrNull(List arguments) { 54 | final options = {}; 55 | for (final argument in arguments) { 56 | if (!argument.contains("=")) { 57 | return null; 58 | } 59 | 60 | final key = argument.substring(0, argument.indexOf("=")); 61 | final option = key.toTaskOptionOrNull; 62 | if (option == null) { 63 | return null; 64 | } 65 | 66 | final value = 67 | argument.substring(argument.indexOf("=") + 1, argument.length).trim(); 68 | if (value.isEmpty) { 69 | return null; 70 | } 71 | options[option] = value; 72 | } 73 | 74 | return options; 75 | } 76 | 77 | /// Return the absolute path specified by [TaskOption.root] 78 | /// or [Context.workingDirectory] if option is not present. 79 | String findPathToRoot(Context context, Map options) { 80 | final Directory Function(Context)? workingDirectorySupplier = 81 | options[TaskOption.root]; 82 | final workingDirectory = workingDirectorySupplier != null 83 | ? workingDirectorySupplier(context) 84 | : context.workingDirectory; 85 | return workingDirectory.absolutePath; 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/cli/flutter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/common.dart"; 24 | 25 | /// Create a new flutter project. 26 | Future createFlutterProjectOrThrow({ 27 | required Executor executor, 28 | required String pathToFlutter, 29 | required String pathToRoot, 30 | required String name, 31 | required String group, 32 | }) async { 33 | executor 34 | ..executable = pathToFlutter 35 | ..workingDirectory = Directory(pathToRoot) 36 | ..arguments = [ 37 | "create", 38 | name, 39 | "--org", 40 | group, 41 | "--template=plugin", 42 | "--platforms=android,ios", 43 | ] 44 | ..run(); 45 | 46 | return Directory(pathToRoot.normalize).resolveDirectory(name) 47 | ..verifyDirectoryExists; 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/cli/runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // ignore_for_file: avoid_print 22 | 23 | import "dart:io"; 24 | 25 | import "../common/common.dart"; 26 | import "cli.dart"; 27 | import "context.dart"; 28 | 29 | /// Run the user command. 30 | Future run( 31 | List args, { 32 | Directory? workingDirectoryOrNull, 33 | TaskService? taskServiceOrNull, 34 | Executor? executor, 35 | GetFlutterSDK? getFlutterSDK, 36 | }) async { 37 | final arguments = [...args]; 38 | final firstArgument = arguments.removeAt(0); 39 | final taskName = firstArgument.toTaskNameOrNull; 40 | final workingDirectory = workingDirectoryOrNull ?? Directory.current; 41 | if (taskName == TaskName.gradle) { 42 | print("executing gradle task..."); 43 | final gradlew = 44 | workingDirectory.resolveFile("gradlew").verifyFileExists.absolutePath; 45 | (executor ?? Executor()) 46 | ..workingDirectory = workingDirectory 47 | ..arguments = arguments 48 | ..executable = gradlew 49 | ..run(); 50 | return "finished executing gradle task"; 51 | } 52 | 53 | if (taskName == TaskName.flutter) { 54 | print("executing flutter task..."); 55 | final flutterTask = getFlutterSDK ?? GetFlutterSDK(); 56 | final flutterResult = 57 | await flutterTask.executeOrThrow(Context(workingDirectory, {})); 58 | final flutter = 59 | flutterResult.resolveFile("flutter/bin/flutter".normalize).absolutePath; 60 | (executor ?? Executor()) 61 | ..workingDirectory = workingDirectory 62 | ..arguments = arguments 63 | ..executable = flutter 64 | ..run(); 65 | return "finished executing flutter task"; 66 | } 67 | 68 | final context = toContextOrNull(workingDirectory, arguments); 69 | final taskService = taskServiceOrNull ?? TaskService(); 70 | if (firstArgument.toLowerCase() == "help") { 71 | return taskService.displayKradlewHelpText; 72 | } else if (taskName == null) { 73 | return "received unknown task name: $firstArgument\nuse kradle help for more information"; 74 | } else if (context == null) { 75 | final arguments = args.sublist(1, args.length); 76 | return "received invalid task options: $arguments\nuse kradle help for more information"; 77 | } else { 78 | return execute(taskName, context, taskService); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/cli/task.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "../common/exception.dart"; 22 | import "cli.dart"; 23 | import "context.dart"; 24 | 25 | /// Interface to encapsulate CLI task functionality. 26 | /// {@category tasks} 27 | abstract class Task { 28 | /// Create a new [Task]. 29 | Task(this.taskName, this.taskOptions); 30 | 31 | /// The name of this task. 32 | final TaskName taskName; 33 | 34 | /// All acceptable options for this [taskName]. 35 | final Map taskOptions; 36 | 37 | /// Task logic implemented by the child class which will be executed. 38 | Future toBeExecuted(Context context, Map options); 39 | 40 | /// Execute the task. 41 | Future> execute(Context context) async { 42 | try { 43 | final output = await executeOrThrow(context); 44 | return TaskResult(isOk: true, output: output); 45 | } on KlutterException catch (e) { 46 | return TaskResult(isOk: false, message: e.cause); 47 | } 48 | } 49 | 50 | /// Execute the task and return instance of [T] or throw 51 | /// [KlutterException] if unsuccessful. 52 | Future executeOrThrow(Context context) async { 53 | return toBeExecuted(context, _getOptions(context)); 54 | } 55 | 56 | /// The validated options. 57 | /// 58 | /// Returns a map with each option having a value either 59 | /// as given by the user of a default value. 60 | /// 61 | /// When value is required and is not given by the user 62 | /// then a [KlutterException] is thrown. 63 | Map _getOptions(Context context) { 64 | final optionsInputCopy = context.taskOptions; 65 | final options = {}; 66 | final inputMissing = []; 67 | final inputInvalid = {}; 68 | final inputUnsupported = [...optionsInputCopy.keys]; 69 | 70 | taskOptions.forEach((key, value) { 71 | final hasUserInput = optionsInputCopy.containsKey(key); 72 | inputUnsupported.remove(key); 73 | 74 | if (hasUserInput) { 75 | final inputValue = optionsInputCopy.remove(key)!; 76 | try { 77 | options[key] = value.convertOrThrow(inputValue); 78 | } on InputException catch (e) { 79 | inputInvalid[key] = e.cause; 80 | } 81 | } else if (value is RequiredUserInput) { 82 | inputMissing.add(key); 83 | } else if (value is UserInputOrDefault) { 84 | options[key] = value.defaultValue; 85 | } else { 86 | throw InputException("unsupported value: $value"); 87 | } 88 | }); 89 | 90 | final inputErrors = [ 91 | ...inputMissing.map((opt) => "missing value for option: ${opt.name}"), 92 | ...inputUnsupported.map((opt) => 93 | "option not supported for task ${taskName.name}: ${opt.name}") 94 | ]; 95 | 96 | inputInvalid.forEach((opt, msg) { 97 | inputErrors.add("invalid value for option ${opt.name}: $msg"); 98 | }); 99 | 100 | if (inputErrors.isNotEmpty) { 101 | throw KlutterException( 102 | "unable to run task ${taskName.name} because: $inputErrors"); 103 | } 104 | 105 | return options; 106 | } 107 | 108 | @override 109 | String toString() { 110 | final buffer = StringBuffer()..writeln(taskName.name); 111 | taskOptions.forEach((option, input) { 112 | buffer.write(" ${option.name}".padRight(20)); 113 | if (input is RequiredUserInput) { 114 | buffer.write("(Required) ${input.description}.\n"); 115 | } else if (input is UserInputOrDefault) { 116 | buffer.write( 117 | "(Optional) ${input.description}. Defaults to '${input.defaultValueToString}'.\n"); 118 | } 119 | }); 120 | return buffer.toString(); 121 | } 122 | } 123 | 124 | /// List of available tasks. 125 | /// 126 | /// The task functionality depends on the calling script. 127 | /// {@category tasks} 128 | enum TaskName { 129 | /// Tasks which adds libraries. 130 | add, 131 | 132 | /// Tasks which gets dependencies (e.g. Flutter SDK). 133 | get, 134 | 135 | /// Tasks which does project initialization (setup). 136 | init, 137 | 138 | /// Clean one or more directories by deleting contents recursively. 139 | clean, 140 | 141 | /// Create a new klutter project. 142 | create, 143 | 144 | /// Build a klutter project. 145 | build, 146 | 147 | /// Run flutter commands using the cached flutter distribution. 148 | flutter, 149 | 150 | /// Run gradle commands using the local gradlew distribution. 151 | gradle, 152 | } 153 | 154 | /// Convert a String value to a [TaskName]. 155 | /// {@category tasks} 156 | extension TaskNameParser on String? { 157 | /// Find a [TaskName] that matches the current 158 | /// (trimmed) String value or return null. 159 | TaskName? get toTaskNameOrNull { 160 | if (this == null) { 161 | return null; 162 | } 163 | 164 | switch (this!.trim().toUpperCase()) { 165 | case "ADD": 166 | return TaskName.add; 167 | case "BUILD": 168 | return TaskName.build; 169 | case "CLEAN": 170 | return TaskName.clean; 171 | case "CREATE": 172 | return TaskName.create; 173 | case "GET": 174 | return TaskName.get; 175 | case "INIT": 176 | return TaskName.init; 177 | case "GRADLE": 178 | return TaskName.gradle; 179 | case "FLUTTER": 180 | return TaskName.flutter; 181 | default: 182 | return null; 183 | } 184 | } 185 | } 186 | 187 | /// List of available scripts options. 188 | /// 189 | /// Each task [TaskName] has 0 or more compatible [TaskOption]. 190 | /// 191 | /// {@category consumer} 192 | /// {@category producer} 193 | /// {@category tasks} 194 | enum TaskOption { 195 | /// The Klutter (Gradle) BOM version. 196 | bom, 197 | 198 | /// The Kradle cache directory. 199 | cache, 200 | 201 | /// For testing purposes. 202 | /// 203 | /// Skips downloading of libraries when set to true. 204 | dryRun, 205 | 206 | /// The Flutter SDK distribution in format 207 | /// major.minor.patch.platform.architecture 208 | /// or major.minor.patch. 209 | /// 210 | /// Example format: 211 | /// ```dart 212 | /// 3.10.6.macos.arm64. 213 | /// 3.10.6 214 | /// ``` 215 | flutter, 216 | 217 | /// The klutter project group name. 218 | group, 219 | 220 | /// The klutter pub version in format major.minor.patch. 221 | klutter, 222 | 223 | /// The klutter-ui pub version in format major.minor.patch. 224 | klutterui, 225 | 226 | /// Name of library to add. 227 | /// 228 | /// Used when adding a Klutter Library as dependency. 229 | lib, 230 | 231 | /// The klutter project name. 232 | name, 233 | 234 | /// To overwrite existing entities or not. 235 | overwrite, 236 | 237 | /// The klutter project root directory. 238 | root, 239 | 240 | /// The squint pub version in format major.minor.patch. 241 | squint, 242 | 243 | /// The iOS version to use. 244 | ios, 245 | } 246 | 247 | /// Convert a String value to a [TaskName]. 248 | extension TaskOptionParser on String? { 249 | /// Find a [TaskName] that matches the current 250 | /// (trimmed) String value or return null. 251 | TaskOption? get toTaskOptionOrNull { 252 | if (this == null) { 253 | return null; 254 | } 255 | 256 | switch (this!.trim().toUpperCase()) { 257 | case "BOM": 258 | return TaskOption.bom; 259 | case "CACHE": 260 | return TaskOption.cache; 261 | case "DRYRUN": 262 | return TaskOption.dryRun; 263 | case "FLUTTER": 264 | return TaskOption.flutter; 265 | case "GROUP": 266 | return TaskOption.group; 267 | case "IOS": 268 | return TaskOption.ios; 269 | case "KLUTTER": 270 | return TaskOption.klutter; 271 | case "KLUTTERUI": 272 | return TaskOption.klutterui; 273 | case "LIB": 274 | return TaskOption.lib; 275 | case "NAME": 276 | return TaskOption.name; 277 | case "OVERWRITE": 278 | return TaskOption.overwrite; 279 | case "ROOT": 280 | return TaskOption.root; 281 | case "SQUINT": 282 | return TaskOption.squint; 283 | default: 284 | return null; 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/src/cli/task_add.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "../common/project.dart"; 22 | import "../common/utilities.dart"; 23 | import "../consumer/android.dart"; 24 | import "context.dart"; 25 | import "option.dart"; 26 | import "task.dart"; 27 | 28 | /// Task to add a Klutter-made Flutter plugin to a Flutter project. 29 | /// 30 | /// {@category consumer} 31 | /// {@category tasks} 32 | class AddLibrary extends Task { 33 | /// Create new Task based of the root folder. 34 | AddLibrary() 35 | : super(TaskName.add, { 36 | TaskOption.lib: const LibraryName(), 37 | TaskOption.root: RootDirectoryInput(), 38 | }); 39 | 40 | @override 41 | Future toBeExecuted( 42 | Context context, Map options) async { 43 | final pathToRoot = findPathToRoot(context, options); 44 | final pluginName = options[TaskOption.lib]; 45 | final location = findDependencyPath( 46 | pathToRoot: pathToRoot, 47 | pluginName: pluginName, 48 | pathToSDK: findFlutterSDK("$pathToRoot/android".normalize), 49 | ); 50 | 51 | // ignore: avoid_print 52 | print("adding klutter library: $pluginName"); 53 | registerPlugin( 54 | pathToRoot: pathToRoot, 55 | pluginName: ":klutter:$pluginName", 56 | pluginLocation: location, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/cli/task_build.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/common.dart"; 24 | import "cli.dart"; 25 | import "context.dart"; 26 | 27 | /// Build the klutter application by running a gradle build. Running 28 | /// this command does everything which is required to start a klutter 29 | /// app on a device. 30 | /// 31 | /// This command uses the project gradle wrapper distribution to 32 | /// run clean and build tasks in the ./platform module. The platform 33 | /// module applies the klutter gradle plugin which handles the code 34 | /// analysis, code generation and copying of artifacts to android 35 | /// and ios sub modules. 36 | /// 37 | /// {@category gradle}. 38 | /// {@category tasks} 39 | class BuildProject extends Task { 40 | /// Create new Task. 41 | BuildProject({Executor? executor}) 42 | : super(TaskName.build, { 43 | TaskOption.root: RootDirectoryInput(), 44 | }) { 45 | _executor = executor ?? Executor(); 46 | } 47 | 48 | late final Executor _executor; 49 | 50 | @override 51 | Future toBeExecuted( 52 | Context context, Map options) async { 53 | final workingDirectory = Directory(findPathToRoot(context, options)); 54 | // ignore: avoid_print 55 | print("building platform module..."); 56 | _executor 57 | ..workingDirectory = workingDirectory 58 | ..arguments = ["clean", "build", "-p", "platform"] 59 | ..executable = Directory(findPathToRoot(context, options)) 60 | .resolveFile("gradlew") 61 | .absolutePath 62 | ..run(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/cli/task_clean_cache.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // ignore_for_file: avoid_print 22 | 23 | import "dart:io"; 24 | 25 | import "../common/common.dart"; 26 | import "cli.dart"; 27 | import "context.dart"; 28 | 29 | /// Clean the kradle cache by deleting contents recursively. 30 | /// {@category tasks} 31 | class CleanCache extends Task { 32 | /// Create new Task. 33 | CleanCache([CacheProvider? cacheProvider]) 34 | : super(TaskName.clean, { 35 | TaskOption.root: RootDirectoryInput(), 36 | }) { 37 | _cacheProvider = cacheProvider ?? CacheProvider(); 38 | } 39 | 40 | late final CacheProvider _cacheProvider; 41 | 42 | @override 43 | Future toBeExecuted( 44 | Context context, Map options) async { 45 | final deleted = []; 46 | final notDeletedByError = {}; 47 | void deleteIfExists(FileSystemEntity entity) { 48 | if (entity.existsSync()) { 49 | try { 50 | entity.deleteSync(recursive: true); 51 | deleted.add(entity); 52 | } on Exception catch (e) { 53 | notDeletedByError[entity] = e.toString(); 54 | } 55 | } 56 | } 57 | 58 | print("cleaning .kradle cache"); 59 | _cacheProvider.getCacheContent(context, options).forEach(deleteIfExists); 60 | for (final element in deleted) { 61 | print("deleted: ${element.absolutePath}"); 62 | } 63 | 64 | notDeletedByError.forEach((key, value) { 65 | print("failed to delete $key, because $value"); 66 | }); 67 | 68 | return CleanCacheResult(deleted, notDeletedByError); 69 | } 70 | } 71 | 72 | /// Result of task [CleanCache]. 73 | /// {@category tasks} 74 | class CleanCacheResult { 75 | /// Create a new instance of [CleanCacheResult]. 76 | const CleanCacheResult(this.deleted, this.notDeletedByError); 77 | 78 | /// List of deleted entities. 79 | final List deleted; 80 | 81 | /// Map of not deleted entities and the corresponding error message. 82 | final Map notDeletedByError; 83 | } 84 | 85 | /// Wrapper to provide the kradle cache directory 86 | /// based on [Context] and [TaskOption] input. 87 | /// {@category tasks} 88 | class CacheProvider { 89 | /// Return the files and directory in the kradle cache directory. 90 | List getCacheContent( 91 | Context context, Map options) { 92 | final cache = Directory(findPathToRoot(context, options)).kradleCache; 93 | return cache.listSync(recursive: true); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/cli/task_result.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// Result object indicating a Task was executed successfully or not. 22 | /// 23 | /// [isOk] is true when task is finished without exceptions or false when not. 24 | /// [message] contains the exception message or is null when task was successful. 25 | /// {@category tasks} 26 | class TaskResult { 27 | /// Create a new result with a message if there was an exception. 28 | const TaskResult({ 29 | required this.isOk, 30 | this.output, 31 | this.message, 32 | }); 33 | 34 | /// Is true when successful or false when not. 35 | final bool isOk; 36 | 37 | /// Exception message if there is any. 38 | final String? message; 39 | 40 | /// Nullable output [T] of task. 41 | final T? output; 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/cli/task_service.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "../common/exception.dart"; 22 | import "../common/utilities.dart"; 23 | import "task.dart"; 24 | import "task_add.dart"; 25 | import "task_build.dart"; 26 | import "task_clean_cache.dart"; 27 | import "task_get_flutter.dart"; 28 | import "task_project_create.dart"; 29 | import "task_project_init.dart"; 30 | 31 | /// Service for available tasks. 32 | /// {@category tasks} 33 | class TaskService { 34 | /// Get the [Task] with [TaskName] or null. 35 | Task? toTask(TaskName taskName) { 36 | final matching = allTasks().where((task) => task.taskName == taskName); 37 | return matching.isNotEmpty ? matching.first : null; 38 | } 39 | 40 | /// Output all tasks and options with descriptions. 41 | String get displayKradlewHelpText { 42 | final buffer = StringBuffer(""" 43 | |Manage your klutter project. 44 | | 45 | |Usage: kradlew [option=value] 46 | | 47 | |""" 48 | .format); 49 | 50 | for (final task in allTasks()) { 51 | buffer.writeln(task.toString()); 52 | } 53 | 54 | return buffer.toString(); 55 | } 56 | 57 | /// List of all [Task] objects. 58 | /// 59 | /// A task may contain one or more sub-task implementations. 60 | /// 61 | /// {@category producer} 62 | /// {@category consumer} 63 | /// {@category tasks} 64 | Set allTasks( 65 | 66 | /// Injectable Task List for testing purposes. 67 | /// Any calling class should omit this parameter 68 | /// and let the function default to [allTasks]. 69 | [List? tasks]) { 70 | final list = tasks ?? 71 | [ 72 | AddLibrary(), 73 | ProjectInit(), 74 | GetFlutterSDK(), 75 | CreateProject(), 76 | BuildProject(), 77 | CleanCache() 78 | ] 79 | ..verifyNoDuplicates; 80 | return list.toSet(); 81 | } 82 | } 83 | 84 | extension on List { 85 | /// Verify there are no overlapping tasks e.g. 86 | /// multiple tasks that have the same TaskName. 87 | /// 88 | /// TaskName should be unique. 89 | /// Any sub-tasks should be defined in the 90 | /// Task Class implementation. 91 | /// 92 | /// Throws [KlutterException] if duplicates are found. 93 | void get verifyNoDuplicates { 94 | final tasks = TaskName.values.toList(growable: true); 95 | for (final taskEntry in this) { 96 | if (!tasks.remove(taskEntry.taskName)) { 97 | throw KlutterException( 98 | """ 99 | |TaskService configuration failure. 100 | |Invalid or duplicate TaskName encountered. 101 | |- TaskName: '${taskEntry.taskName}' 102 | """ 103 | .format, 104 | ); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/common/common.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// Common code used by more than one internal library 22 | /// which is not a specific implementation for either 23 | /// consumer or producer. 24 | library common; 25 | 26 | export "config.dart"; 27 | export "environment.dart"; 28 | export "exception.dart"; 29 | export "executor.dart"; 30 | export "project.dart"; 31 | export "utilities.dart"; 32 | -------------------------------------------------------------------------------- /lib/src/common/config.dart: -------------------------------------------------------------------------------- 1 | import "package:meta/meta.dart"; 2 | 3 | import "../../klutter.dart"; 4 | 5 | /// The version of the Klutter Pub Plugin. 6 | const klutterPubVersion = "3.0.2"; 7 | 8 | /// The version of the Klutter UI Pub Plugin. 9 | const klutterUIPubVersion = "1.1.0"; 10 | 11 | /// The version of the squint_json Pub Plugin. 12 | const squintPubVersion = "0.1.2"; 13 | 14 | /// The version of the Klutter Gradle Plugin. 15 | const klutterGradleVersion = "2024.1.3.beta"; 16 | 17 | /// The default Flutter version to be used in the Klutter project. 18 | const klutterFlutterVersion = "3.10.6"; 19 | 20 | /// The version of Kotlin to be used. 21 | const kotlinVersion = "1.9.10"; 22 | 23 | /// The minimum SDK version for Android. 24 | const androidMinSdk = 24; 25 | 26 | /// The compile SDK version for Android. 27 | const androidCompileSdk = 33; 28 | 29 | /// The minimum iOS version. 30 | const iosVersion = 13.0; 31 | 32 | /// Flutter SDK versions which can be used for a Producer project. 33 | const supportedFlutterVersions = { 34 | "3.0.5": Version(major: 3, minor: 0, patch: 5), 35 | "3.3.10": Version(major: 3, minor: 3, patch: 10), 36 | "3.7.12": Version(major: 3, minor: 7, patch: 12), 37 | "3.10.6": Version(major: 3, minor: 10, patch: 6), 38 | }; 39 | 40 | /// Verify if version input is valid. 41 | extension VersionVerifier on String { 42 | /// Verify if the version matches: 43 | /// 4 digits dot 1 or 2 digits dot 1 or 2 digits and optionally a dot plus postfix. 44 | /// 45 | /// Examples: 46 | /// - 2023.3.1.beta 47 | /// - 2024.2.15 48 | String? get verifyBomVersion { 49 | final regex = 50 | RegExp(r"""([0-9]){4}[\\.][0-9]{1,2}[\\.][0-9]{1,2}([\\.]\w+|)$"""); 51 | if (regex.firstMatch(this) == null) { 52 | return null; 53 | } else { 54 | return this; 55 | } 56 | } 57 | 58 | /// Verify if the version is in format 59 | /// major.minor.patch or 60 | /// major.minor.patch.os.arch. 61 | /// 62 | /// Examples: 63 | /// - 3.10.6 64 | /// - 2.16.77 65 | /// - 2.16.77.windows.x64 66 | VerifiedFlutterVersion? get verifyFlutterVersion { 67 | final version = supportedFlutterVersions[this]; 68 | if (version != null) { 69 | return VerifiedFlutterVersion(version); 70 | } 71 | 72 | for (final os in OperatingSystem.values) { 73 | for (final arch in Architecture.values) { 74 | for (final version in supportedFlutterVersions.values) { 75 | if (this == "${version.prettyPrint}.${os.name}.${arch.name}") { 76 | return VerifiedFlutterVersion(version, os: os, arch: arch); 77 | } 78 | } 79 | } 80 | } 81 | 82 | return null; 83 | } 84 | } 85 | 86 | /// Wrapper for [VersionVerifier.verifyFlutterVersion] result. 87 | class VerifiedFlutterVersion { 88 | /// Construct a new instance of [VerifiedFlutterVersion]. 89 | const VerifiedFlutterVersion(this.version, {this.os, this.arch}); 90 | 91 | /// The Flutter version in format major.minor.patch. 92 | final Version version; 93 | 94 | /// The OperatingSystem extracted from the version String. 95 | final OperatingSystem? os; 96 | 97 | /// The Architecture extracted from the version String. 98 | final Architecture? arch; 99 | 100 | @override 101 | String toString() => "VerifiedFlutterVersion($version, $os, $arch)"; 102 | } 103 | 104 | /// Version data class. 105 | @immutable 106 | class Version implements Comparable { 107 | /// Create a new [Version] instance. 108 | const Version({ 109 | required this.major, 110 | required this.minor, 111 | required this.patch, 112 | }); 113 | 114 | /// Create a new [Version] instance from a [prettyPrint] String. 115 | factory Version.fromString(String prettyPrintedString) { 116 | final regex = RegExp(r"^(\d+[.]\d+[.]\d+$)"); 117 | if (!regex.hasMatch(prettyPrintedString)) { 118 | throw KlutterException( 119 | "String is not formatted as expected (major.minor.patch.os.arch): $prettyPrintedString"); 120 | } 121 | 122 | final data = prettyPrintedString.split("."); 123 | return Version( 124 | major: int.parse(data[0]), 125 | minor: int.parse(data[1]), 126 | patch: int.parse(data[2])); 127 | } 128 | 129 | /// Major version which is the first part of a version. 130 | /// 131 | /// A major version change indicates breaking changes. 132 | final int major; 133 | 134 | /// Minor version which is the middle part of a version. 135 | /// 136 | /// A minor version change indicates backwards-compatible features added. 137 | final int minor; 138 | 139 | /// Path version which is the last part of a version. 140 | /// 141 | /// A patch version change indicates technical changes or bug fixes. 142 | final int patch; 143 | 144 | /// Return formatted version string. 145 | String get prettyPrint => "$major.$minor.$patch"; 146 | 147 | @override 148 | String toString() => "Version($prettyPrint)"; 149 | 150 | @override 151 | bool operator ==(Object other) { 152 | if (other is! Version) { 153 | return false; 154 | } 155 | 156 | if (other.major != major) { 157 | return false; 158 | } 159 | 160 | if (other.minor != minor) { 161 | return false; 162 | } 163 | 164 | return other.patch == patch; 165 | } 166 | 167 | @override 168 | int get hashCode => major; 169 | 170 | @override 171 | int compareTo(Version other) { 172 | if (major != other.major) { 173 | return other.major.compareTo(major); 174 | } 175 | 176 | if (minor != other.minor) { 177 | return other.minor.compareTo(minor); 178 | } 179 | 180 | return other.patch.compareTo(patch); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /lib/src/common/environment.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:core"; 22 | import "dart:io"; 23 | 24 | /// Wrapper for [Platform] to access Operating System properties. 25 | PlatformWrapper platform = PlatformWrapper(); 26 | 27 | /// Wrapper for [Platform]. 28 | class PlatformWrapper { 29 | /// Stub for environment properties. 30 | Map environmentMap = Platform.environment; 31 | 32 | /// Get the current Operating System through [Platform.operatingSystem]. 33 | String get operatingSystem => Platform.operatingSystem; 34 | 35 | /// Get the environment variables through [Platform.environment]. 36 | Map get environment => environmentMap; 37 | 38 | /// Check if current platform is windows. 39 | bool get isWindows => Platform.isWindows; 40 | 41 | /// Check if current platform is macos. 42 | bool get isMacos => Platform.isMacOS; 43 | 44 | /// Check if current platform is linux. 45 | bool get isLinux => Platform.isLinux; 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/common/exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// Exception indicating a problem with the Klutter Framework 22 | /// either internally or by faulty configuration. 23 | class KlutterException implements Exception { 24 | /// Create instance of [KlutterException] with a message [cause]. 25 | const KlutterException(this.cause); 26 | 27 | /// Message explaining the cause of the exception. 28 | final String cause; 29 | 30 | @override 31 | String toString() => "KlutterException with cause: '$cause'"; 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/common/executor.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "exception.dart"; 24 | import "utilities.dart"; 25 | 26 | /// Wrapper for using the commandline. 27 | class Executor { 28 | /// The directory from which to execute the command. 29 | Directory? workingDirectory; 30 | 31 | /// The executable to run. 32 | /// 33 | /// For example: "flutter", "gradlew", etc. 34 | /// 35 | /// Can be relative to the [workingDirectory] or an absolute path. 36 | String? executable; 37 | 38 | /// An (optional) list of arguments. 39 | /// 40 | /// For example: "clean", "build" with [executable] "gradlew" 41 | /// would result in a gradlew clean build to be executed from the [workingDirectory]. 42 | List arguments = const []; 43 | 44 | /// Run the command. 45 | /// 46 | /// For example an [executable] 'gradlew' 47 | /// with [arguments] "clean", "build", "-p", "platform" 48 | /// and [workingDirectory] './Users/Foo' 49 | /// would result in command "./Users/Foo/gradlew clean build -p platform". 50 | ProcessResult run() { 51 | if (executable == null) { 52 | throw const KlutterException("Executor field 'executable' is null"); 53 | } 54 | 55 | if (workingDirectory == null) { 56 | throw const KlutterException("Executor field 'workingDirectory' is null"); 57 | } 58 | 59 | final result = Process.runSync( 60 | executable!, 61 | arguments, 62 | runInShell: true, 63 | workingDirectory: workingDirectory!.absolutePath, 64 | ); 65 | 66 | stdout.write(result.stdout); 67 | stderr.write(result.stderr); 68 | return result; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/common/utilities.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "exception.dart"; 24 | 25 | /// File management utilities. 26 | extension FileUtil on FileSystemEntity { 27 | /// Execute a fallback function if FileSystemEntity does not exist. 28 | void ifNotExists(void Function(FileSystemEntity file) doElse) { 29 | if (!existsSync()) { 30 | doElse.call(this); 31 | } 32 | } 33 | 34 | /// Return FileSystemEntity or execute a fallback function if it does not exist. 35 | FileSystemEntity orElse(void Function(FileSystemEntity file) doElse) { 36 | ifNotExists(doElse); 37 | return this; 38 | } 39 | 40 | /// Create an absolute path to the given file. 41 | /// 42 | /// If the path does not exist throw a [KlutterException]. 43 | File get verifyFileExists => File(absolutePath) 44 | ..ifNotExists((file) { 45 | throw KlutterException("Path does not exist: ${file.absolute.path}"); 46 | }); 47 | 48 | /// Create an absolute path to the given folder. 49 | /// 50 | /// If the path does not exist throw a [KlutterException]. 51 | Directory get verifyDirectoryExists => Directory(absolutePath) 52 | ..ifNotExists((dir) { 53 | throw KlutterException("Path does not exist: ${dir.absolute.path}"); 54 | }); 55 | 56 | /// Check if the Directory exists and if not create it recursively. 57 | FileSystemEntity get maybeCreate { 58 | ifNotExists((fse) { 59 | if (fse is Directory) { 60 | fse.createSync(recursive: true); 61 | } else if (fse is File) { 62 | fse.createSync(recursive: true); 63 | } 64 | }); 65 | return this; 66 | } 67 | 68 | /// Check if the Directory exists and then delete it. 69 | FileSystemEntity get maybeDelete { 70 | if (existsSync()) { 71 | deleteSync(recursive: true); 72 | } 73 | return this; 74 | } 75 | 76 | /// Return absolute path of current File or Folder as String. 77 | String get absolutePath => absolute.path; 78 | 79 | /// Return absolute path of current File or Directory with all 80 | /// slashes ('/' or '\') replaced for the platform specific separator. 81 | File get normalizeToFile => File(_substitute); 82 | 83 | /// Return absolute path of current File or Directory with all 84 | /// slashes ('/' or '\') replaced for the platform specific separator. 85 | Directory get normalizeToDirectory => Directory(_substitute); 86 | 87 | /// Return a normalized path of this folder to the given filename. 88 | File resolveFile(String filename) => 89 | File("$absolutePath/$filename").normalizeToFile; 90 | 91 | /// Return a normalized path of this folder to the given filename. 92 | Directory resolveDirectory(String folder) => 93 | Directory("$absolutePath/$folder").normalizeToDirectory; 94 | 95 | /// Convert a path String by removing all '..' and moving up a folder for each. 96 | String get _substitute { 97 | final normalized = []; 98 | final parts = absolute.path.replaceAll(r"""\""", "/").split("/") 99 | ..removeWhere((e) => e.isEmpty); 100 | 101 | for (final part in parts) { 102 | if (part.trim() == "..") { 103 | normalized.removeLast(); 104 | } else { 105 | normalized.add(part); 106 | } 107 | } 108 | 109 | final path = normalized.join(Platform.pathSeparator); 110 | 111 | if (Platform.isWindows) { 112 | return path; 113 | } 114 | 115 | if (path.startsWith(Platform.pathSeparator)) { 116 | return path; 117 | } 118 | 119 | return Platform.pathSeparator + path; 120 | } 121 | } 122 | 123 | /// Utils for easier Directory handling. 124 | extension DirectoryUtil on Directory { 125 | /// Check if directory contains items. 126 | bool get isEmpty => listSync().isEmpty; 127 | } 128 | 129 | /// Utils for easier String manipulation. 130 | extension StringUtil on String { 131 | /// Create an absolute path to the given file or folder. 132 | /// 133 | /// If the path does not exist throw a [KlutterException]. 134 | String get verifyExists => Directory(this) 135 | 136 | // If not a Directory check if it is an existing File. 137 | .orElse((folder) => File(this) 138 | 139 | // If not a File then forget about it. 140 | .orElse((file) => throw KlutterException( 141 | "Path does not exist: ${file.absolute.path}", 142 | ))) 143 | .absolutePath; 144 | 145 | /// Utility to print templated Strings. 146 | /// 147 | /// Example: 148 | /// 149 | /// Given a templated String: 150 | /// ``` 151 | /// final String foo = """|A multi-line message 152 | /// |is a message 153 | /// |that exists of 154 | /// |multiple lines. 155 | /// | 156 | /// |True story""""; 157 | /// ``` 158 | /// 159 | /// Will produce a multi-line String: 160 | /// 161 | /// 'A multi-line message 162 | /// is a message 163 | /// that exists of 164 | /// multiple lines. 165 | /// 166 | /// True story' 167 | String get format => replaceAllMapped( 168 | // Find all '|' char including preceding whitespaces. 169 | RegExp(r"(\s+?\|)"), 170 | // Replace them with a single linebreak. 171 | (_) => "\n") 172 | .replaceAll(RegExp(r"(!?,)\|"), "") 173 | .trimLeft() 174 | .replaceAll(",|", "|"); 175 | 176 | /// Return current String value as being a path to a File or Directory 177 | /// with forward slashes ('/') replaced for the platform specific separator. 178 | String get normalize => replaceAll("/", Platform.pathSeparator); 179 | 180 | /// Return current String value with 'Plugin' suffix if not present. 181 | String get suffixedWithPlugin { 182 | if (endsWith("Plugin")) { 183 | return this; 184 | } else { 185 | return "${this}Plugin"; 186 | } 187 | } 188 | } 189 | 190 | /// Download a File. 191 | Future download(String endpoint, File target) async { 192 | final client = HttpClient(); 193 | final request = await client.getUrl(Uri.parse(endpoint)); 194 | final response = await request.close(); 195 | await response.pipe(target.openWrite()); 196 | } 197 | 198 | /// Unzip a File. 199 | Future unzip(File zip, Directory target) async { 200 | await Process.run( 201 | "tar", 202 | ["-xf", zip.absolutePath, "-p"], 203 | runInShell: true, 204 | workingDirectory: target.absolutePath, 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /lib/src/consumer/consumer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// The consumer library contains everything required 22 | /// to usage (consume) plugins created with Klutter. 23 | /// 24 | /// {@category consumer} 25 | library consumer; 26 | 27 | export "android.dart"; 28 | export "ios.dart"; 29 | -------------------------------------------------------------------------------- /lib/src/consumer/ios.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/common.dart"; 24 | 25 | /// Create the root/ios/Klutter directory and add a readme file. 26 | /// 27 | /// {@category consumer} 28 | void setIosVersionInPodFile(Directory iosDirectory, double version) { 29 | final podFile = iosDirectory.resolveFile("Podfile"); 30 | if (podFile.existsSync()) { 31 | podFile.setIosVersion(version); 32 | } 33 | } 34 | 35 | extension on File { 36 | void setIosVersion(double version) { 37 | // INPUT 38 | final lines = readAsLinesSync(); 39 | 40 | // OUTPUT 41 | final newLines = []; 42 | 43 | // Used to check if adding framework is done. 44 | var hasExplicitPlatformVersion = false; 45 | 46 | for (final line in lines) { 47 | final trimmed = line.replaceAll(" ", ""); 48 | // Check if line sets ios platform version 49 | // and if so then update the version. 50 | if (trimmed.contains("platform:ios,")) { 51 | newLines.add("platform :ios, '$version'"); 52 | hasExplicitPlatformVersion = true; 53 | } else { 54 | newLines.add(line); 55 | } 56 | } 57 | 58 | if (!hasExplicitPlatformVersion) { 59 | throw const KlutterException("Failed to set ios version in Podfile."); 60 | } 61 | 62 | // Write the edited line to the podspec file. 63 | writeAsStringSync(newLines.join("\n")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/producer/gradle.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/utilities.dart"; 24 | import "resource.dart"; 25 | 26 | /// Copy Gradle files to root and root/android folders to enable the usage of Gradle. 27 | /// 28 | /// Copies the following files to both root and root/android: 29 | /// - gradlew 30 | /// - gradlew.bat 31 | /// - gradle.properties 32 | /// - gradle/wrapper/gradle-wrapper.jar 33 | /// - gradle/wrapper/gradle-wrapper.properties 34 | /// 35 | /// {@category producer} 36 | /// {@category gradle} 37 | class Gradle { 38 | /// Create a Gradle instance based of the Flutter project root folder. 39 | Gradle(this.pathToRoot, this.resourcesDirectory) { 40 | _gradleProperties = LocalResource( 41 | pathToSource: 42 | resourcesDirectory.resolveFile("gradle.properties").absolutePath, 43 | filename: "gradle.properties", 44 | targetRelativeToRoot: ""); 45 | _gradlew = LocalResource( 46 | pathToSource: resourcesDirectory.resolveFile("gradlew").absolutePath, 47 | filename: "gradlew", 48 | targetRelativeToRoot: ""); 49 | _gradlewBat = LocalResource( 50 | pathToSource: 51 | resourcesDirectory.resolveFile("gradlew.bat").absolutePath, 52 | filename: "gradlew.bat", 53 | targetRelativeToRoot: ""); 54 | _gradlewJar = LocalResource( 55 | pathToSource: 56 | resourcesDirectory.resolveFile("gradle-wrapper.jar").absolutePath, 57 | filename: "gradle-wrapper.jar", 58 | targetRelativeToRoot: "gradle/wrapper".normalize); 59 | _gradlewProperties = LocalResource( 60 | pathToSource: resourcesDirectory 61 | .resolveFile("gradle-wrapper.properties") 62 | .absolutePath, 63 | filename: "gradle-wrapper.properties", 64 | targetRelativeToRoot: "gradle/wrapper".normalize); 65 | } 66 | 67 | /// The Flutter project root folder. 68 | final String pathToRoot; 69 | 70 | /// The directory containing the gradle-wrapper files. 71 | final Directory resourcesDirectory; 72 | 73 | late final LocalResource _gradlew; 74 | late final LocalResource _gradlewBat; 75 | late final LocalResource _gradlewJar; 76 | late final LocalResource _gradlewProperties; 77 | late final LocalResource _gradleProperties; 78 | 79 | /// Copy Gradle files to the project root folder. 80 | /// 81 | /// Copies the following files: 82 | /// - gradlew 83 | /// - gradlew.bat 84 | /// - gradle.properties 85 | /// - gradle/wrapper/gradle-wrapper.jar 86 | /// - gradle/wrapper/gradle-wrapper.properties 87 | Future get copyToRoot async { 88 | pathToRoot.verifyExists.rootFolder.copyFiles([ 89 | _gradlew, 90 | _gradlewBat, 91 | _gradlewJar, 92 | _gradleProperties, 93 | _gradlewProperties 94 | ]); 95 | } 96 | 97 | /// Copy Gradle files to the project root/android folder. 98 | /// 99 | /// Copies the following files: 100 | /// - gradlew 101 | /// - gradlew.bat 102 | /// - gradle.properties 103 | /// - gradle/wrapper/gradle-wrapper.jar 104 | /// - gradle/wrapper/gradle-wrapper.properties 105 | Future get copyToAndroid async { 106 | pathToRoot.verifyExists.androidFolder.copyFiles([ 107 | _gradlewBat, 108 | _gradlew, 109 | _gradleProperties, 110 | _gradlewJar, 111 | _gradlewProperties, 112 | ]); 113 | } 114 | } 115 | 116 | extension on String { 117 | Directory get rootFolder => Directory(this); 118 | 119 | Directory get androidFolder => 120 | Directory("$this/android".normalize)..absolutePath.verifyExists; 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/producer/ios.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/common.dart"; 24 | 25 | /// Create the root/ios/Klutter directory and add a readme file. 26 | /// 27 | /// {@category producer} 28 | void createIosKlutterFolder(String pathToIos) => pathToIos.verifyExists 29 | ..createKlutterFolder 30 | ..createKlutterReadmeFile; 31 | 32 | /// Edit the root/ios/.podspec file to depend on the 33 | /// xcframework build by the platform module. 34 | /// 35 | /// The generated framework will be copied to the root/ios/Klutter folder. 36 | /// 37 | /// {@category producer} 38 | void addFrameworkAndSetIosVersionInPodspec( 39 | {required String pathToIos, 40 | required String pluginName, 41 | required double iosVersion}) => 42 | pathToIos.verifyExists 43 | .toPodspec(pluginName) 44 | .addFrameworkAndSetIosVersion(iosVersion); 45 | 46 | extension on String { 47 | void get createKlutterFolder { 48 | Directory("$this/Klutter").normalizeToDirectory.maybeCreate; 49 | } 50 | 51 | void get createKlutterReadmeFile { 52 | File("$this/Klutter/klutter.md").normalizeToFile.maybeCreate; 53 | } 54 | 55 | File toPodspec(String pluginName) => 56 | File("$this/$pluginName.podspec").normalizeToFile 57 | ..ifNotExists((file) { 58 | throw KlutterException("Missing podspec file: ${file.path}"); 59 | }); 60 | } 61 | 62 | extension on File { 63 | void addFrameworkAndSetIosVersion(double iosVersion) { 64 | final regex = RegExp("Pod::Spec.new.+?do.+?.([^|]+?)."); 65 | 66 | /// Check the prefix used in the podspec or default to 's'. 67 | /// 68 | /// By default the podspec file uses 's' as prefix. 69 | /// In case a podspec does not use this default, 70 | /// this regex will find the custom prefix. 71 | /// 72 | /// If not found then 's' is used. 73 | final prefix = regex.firstMatch(readAsStringSync())?.group(1) ?? "s"; 74 | 75 | // INPUT 76 | final lines = readAsLinesSync(); 77 | 78 | // OUTPUT 79 | final newLines = []; 80 | 81 | // Used to check if adding framework is done. 82 | var hasAddedVendoredFramework = lines.any((line) => line 83 | .contains('ios.vendored_frameworks = "Klutter/Platform.xcframework"')); 84 | 85 | for (final line in lines) { 86 | final trimmed = line.replaceAll(" ", ""); 87 | // Check if line sets ios platform version and 88 | // if so then update the version. 89 | if (trimmed.contains("s.platform=:ios,")) { 90 | newLines.add(" s.platform = :ios, '$iosVersion'"); 91 | } else { 92 | newLines.add(line); 93 | } 94 | 95 | // Check if line contains Flutter dependency (which should always be present). 96 | // If so then add the vendored framework dependency. 97 | // This is done so the line is added at a fixed point in the podspec. 98 | if (line.replaceAll(" ", "").contains("$prefix.dependency'Flutter'")) { 99 | if (!hasAddedVendoredFramework) { 100 | newLines.add( 101 | """ $prefix.ios.vendored_frameworks = "Klutter/Platform.xcframework" """, 102 | ); 103 | 104 | hasAddedVendoredFramework = true; 105 | } 106 | } 107 | } 108 | 109 | if (!hasAddedVendoredFramework) { 110 | throw KlutterException( 111 | """ 112 | |Failed to add Platform.framework to ios folder. 113 | | 114 | |Unable to find the following line in file $path: 115 | |- '$prefix.dependency 'Flutter'' 116 | | 117 | |""" 118 | .format, 119 | ); 120 | } 121 | 122 | // Write the editted line to the podspec file. 123 | writeAsStringSync(newLines.join("\n")); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/producer/kradle.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/utilities.dart"; 24 | import "resource.dart"; 25 | 26 | /// Copy Kradle files to root. 27 | /// 28 | /// Copies the following files: 29 | /// - kradlew 30 | /// - kradlew.bat 31 | /// - kradle.env 32 | /// - kradle.yaml 33 | /// - kradle/kradle-wrapper.jar 34 | /// 35 | /// {@category producer} 36 | class Kradle { 37 | /// Create a Kradle instance based of the Flutter project root folder. 38 | Kradle(this.pathToRoot, this.resourcesDirectory) { 39 | _kradleEnv = LocalResource( 40 | pathToSource: resourcesDirectory.resolveFile("kradle.env").absolutePath, 41 | filename: "kradle.env", 42 | targetRelativeToRoot: ""); 43 | _kradleYaml = LocalResource( 44 | pathToSource: 45 | resourcesDirectory.resolveFile("kradle.yaml").absolutePath, 46 | filename: "kradle.yaml", 47 | targetRelativeToRoot: ""); 48 | } 49 | 50 | /// The Flutter project root folder. 51 | final String pathToRoot; 52 | 53 | /// The directory containing the kradle files. 54 | final Directory resourcesDirectory; 55 | 56 | late final LocalResource _kradleEnv; 57 | 58 | late final LocalResource _kradleYaml; 59 | 60 | /// Copy Kradle files to the project root folder. 61 | /// 62 | /// Copies the following files: 63 | /// - kradle.yaml 64 | /// - kradle.env 65 | Future get copyToRoot async { 66 | pathToRoot.verifyExists.rootFolder.copyFiles([ 67 | _kradleYaml, 68 | _kradleEnv, 69 | ]); 70 | } 71 | } 72 | 73 | extension on String { 74 | Directory get rootFolder => Directory(this); 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/producer/producer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /// The producer library contains everything required 22 | /// to create (produce) plugins with Klutter. 23 | /// 24 | /// {@category producer} 25 | library producer; 26 | 27 | export "android.dart"; 28 | export "gradle.dart"; 29 | export "ios.dart"; 30 | export "platform.dart"; 31 | export "project.dart"; 32 | -------------------------------------------------------------------------------- /lib/src/producer/project.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/utilities.dart"; 24 | 25 | /// Generate the main.dart file in the root/example/lib folder. 26 | /// 27 | /// This generated main.dart file uses the example Klutter platform module. 28 | /// 29 | /// {@category producer} 30 | void writeExampleMainDartFile({ 31 | required String pathToExample, 32 | required String pluginName, 33 | }) => 34 | Directory(pathToExample) 35 | .maybeCreate 36 | .createMainDartFile 37 | .writeMainContent(pluginName); 38 | 39 | extension on FileSystemEntity { 40 | /// Create main.dart file in the example/lib folder. 41 | File get createMainDartFile { 42 | Directory("$absolutePath/lib").normalizeToDirectory.maybeCreate; 43 | return File("$absolutePath/lib/main.dart").normalizeToFile 44 | ..ifNotExists((folder) => File(folder.absolutePath).createSync()); 45 | } 46 | } 47 | 48 | extension on File { 49 | /// Write the content of the settings.gradle.kts of a Klutter plugin. 50 | void writeMainContent(String pluginName) { 51 | writeAsStringSync(""" 52 | import 'package:flutter/material.dart'; 53 | | 54 | |import 'package:$pluginName/$pluginName.dart'; 55 | | 56 | |void main() { 57 | | runApp(const MyApp()); 58 | |} 59 | | 60 | |class MyApp extends StatefulWidget { 61 | | const MyApp({Key? key}) : super(key: key); 62 | | 63 | | @override 64 | | State createState() => _MyAppState(); 65 | |} 66 | | 67 | |class _MyAppState extends State { 68 | | String _greeting = "There shall be no greeting for now!"; 69 | | 70 | | @override 71 | | void initState() { 72 | | super.initState(); 73 | | greeting( 74 | | state: this, 75 | | onComplete: (_) => setState(() {}), 76 | | onSuccess: (value) => _greeting = value, 77 | | onFailure: (message) => _greeting = "He did not want to say Hi..." 78 | | ); 79 | | } 80 | | 81 | | @override 82 | | Widget build(BuildContext context) { 83 | | return MaterialApp( 84 | | home: Scaffold( 85 | | appBar: AppBar( 86 | | title: const Text('Plugin example app'), 87 | | ), 88 | | body: Center( 89 | | child: Text(_greeting), 90 | | ), 91 | | ), 92 | | ); 93 | | } 94 | |}""" 95 | .format); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/producer/resource.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "../common/utilities.dart"; 24 | 25 | /// Utility to copy Files from lib/res folder to project. 26 | extension ResourceCopy on Directory { 27 | /// Copy a File from lib/res folder to a project folder. 28 | void copyFiles(List resources) { 29 | for (final resource in resources) { 30 | final from = File(resource.pathToSource.normalize)..verifyFileExists; 31 | final pathTo = 32 | Directory("$absolutePath/${resource.targetRelativeToRoot}".normalize) 33 | ..maybeCreate 34 | ..verifyDirectoryExists; 35 | final to = pathTo.resolveFile(resource.filename); 36 | from.copySync(to.absolutePath); 37 | Process.runSync("chmod", runInShell: true, ["755", to.absolutePath]); 38 | to.verifyFileExists; 39 | } 40 | } 41 | } 42 | 43 | /// Representation of a Gradle file which 44 | /// should be copied to a Klutter project. 45 | class LocalResource { 46 | /// Create a new [LocalResource] instance. 47 | const LocalResource({ 48 | required this.pathToSource, 49 | required this.filename, 50 | required this.targetRelativeToRoot, 51 | }); 52 | 53 | /// Name of File to be copied. 54 | final String filename; 55 | 56 | /// Target path relative to the project root folder where to copy the File. 57 | final String targetRelativeToRoot; 58 | 59 | /// Path to the source File. 60 | final String pathToSource; 61 | } 62 | -------------------------------------------------------------------------------- /logo_animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buijs-dev/klutter-dart/d55440838045406f2653058ea4694beece05bad3/logo_animated.gif -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: klutter 2 | description: Write Flutter plugins for both Android and iOS using Kotlin only. 3 | version: 3.0.2 4 | homepage: https://buijs.dev 5 | repository: https://github.com/buijs-dev/klutter-dart 6 | 7 | executables: 8 | kradle: 9 | 10 | environment: 11 | sdk: ">=2.17.6 <4.0.0" 12 | 13 | dependencies: 14 | dart_style: ^2.2.4 # can be bumped when min SDK is 2.19.0 15 | interact: ^2.2.0 16 | meta: ^1.9.1 17 | 18 | dev_dependencies: 19 | build_runner: ^2.4.0 20 | build_web_compilers: ^4.0.4 21 | coverage: ^1.6.4 22 | dartdoc: ^6.1.0 # can be bumped when min SDK is 2.19.0 23 | pana: ^0.22.2 24 | test: ^1.24.1 -------------------------------------------------------------------------------- /resources.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buijs-dev/klutter-dart/d55440838045406f2653058ea4694beece05bad3/resources.tar.gz -------------------------------------------------------------------------------- /resources.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buijs-dev/klutter-dart/d55440838045406f2653058ea4694beece05bad3/resources.zip -------------------------------------------------------------------------------- /resources/create_tar_resources.sh: -------------------------------------------------------------------------------- 1 | tar -cvf resources.tar.gz gradle.properties gradle-wrapper.jar gradle-wrapper.properties gradlew gradlew.bat kradle.env kradle.yaml -------------------------------------------------------------------------------- /resources/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buijs-dev/klutter-dart/d55440838045406f2653058ea4694beece05bad3/resources/gradle-wrapper.jar -------------------------------------------------------------------------------- /resources/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /resources/gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | 4 | #Kotlin 5 | kotlin.code.style=official 6 | 7 | #Android 8 | android.useAndroidX=true 9 | 10 | #MPP 11 | kotlin.mpp.stability.nowarn=true -------------------------------------------------------------------------------- /resources/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /resources/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /resources/kradle.env: -------------------------------------------------------------------------------- 1 | cache={{system.user.home}}/.kradle/cache/ 2 | output.path={{project.build}}/klutter 3 | skip.codegen=false 4 | protoc.url=https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-universal_binary.zip -------------------------------------------------------------------------------- /resources/kradle.yaml: -------------------------------------------------------------------------------- 1 | bom-version: '2024.1.3.beta' 2 | flutter-version: '3.10.6' 3 | feature-protobuf-enabled: false -------------------------------------------------------------------------------- /rules.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - always_declare_return_types 4 | - always_put_control_body_on_new_line 5 | - always_put_required_named_parameters_first 6 | - always_require_non_null_named_parameters 7 | - always_specify_types 8 | - always_use_package_imports 9 | - annotate_overrides 10 | - avoid_annotating_with_dynamic 11 | - avoid_as 12 | - avoid_bool_literals_in_conditional_expressions 13 | - avoid_catches_without_on_clauses 14 | - avoid_catching_errors 15 | - avoid_classes_with_only_static_members 16 | - avoid_double_and_int_checks 17 | - avoid_dynamic_calls 18 | - avoid_empty_else 19 | - avoid_equals_and_hash_code_on_mutable_classes 20 | - avoid_escaping_inner_quotes 21 | - avoid_field_initializers_in_const_classes 22 | - avoid_function_literals_in_foreach_calls 23 | - avoid_implementing_value_types 24 | - avoid_init_to_null 25 | - avoid_js_rounded_ints 26 | - avoid_null_checks_in_equality_operators 27 | - avoid_positional_boolean_parameters 28 | - avoid_print 29 | - avoid_private_typedef_functions 30 | - avoid_redundant_argument_values 31 | - avoid_relative_lib_imports 32 | - avoid_renaming_method_parameters 33 | - avoid_return_types_on_setters 34 | - avoid_returning_null 35 | - avoid_returning_null_for_future 36 | - avoid_returning_null_for_void 37 | - avoid_returning_this 38 | - avoid_setters_without_getters 39 | - avoid_shadowing_type_parameters 40 | - avoid_single_cascade_in_expression_statements 41 | - avoid_slow_async_io 42 | - avoid_type_to_string 43 | - avoid_types_as_parameter_names 44 | - avoid_types_on_closure_parameters 45 | - avoid_unnecessary_containers 46 | - avoid_unused_constructor_parameters 47 | - avoid_void_async 48 | - avoid_web_libraries_in_flutter 49 | - await_only_futures 50 | - camel_case_extensions 51 | - camel_case_types 52 | - cancel_subscriptions 53 | - cascade_invocations 54 | - cast_nullable_to_non_nullable 55 | - close_sinks 56 | - comment_references 57 | - constant_identifier_names 58 | - control_flow_in_finally 59 | - curly_braces_in_flow_control_structures 60 | - diagnostic_describe_all_properties 61 | - directives_ordering 62 | - do_not_use_environment 63 | - empty_catches 64 | - empty_constructor_bodies 65 | - empty_statements 66 | - exhaustive_cases 67 | - file_names 68 | - flutter_style_todos 69 | - hash_and_equals 70 | - implementation_imports 71 | - invariant_booleans 72 | - iterable_contains_unrelated_type 73 | - join_return_with_assignment 74 | - leading_newlines_in_multiline_strings 75 | - library_names 76 | - library_prefixes 77 | - lines_longer_than_80_chars 78 | - list_remove_unrelated_type 79 | - literal_only_boolean_expressions 80 | - missing_whitespace_between_adjacent_strings 81 | - no_adjacent_strings_in_list 82 | - no_default_cases 83 | - no_duplicate_case_values 84 | - no_logic_in_create_state 85 | - no_runtimeType_toString 86 | - non_constant_identifier_names 87 | - null_check_on_nullable_type_parameter 88 | - null_closures 89 | - omit_local_variable_types 90 | - one_member_abstracts 91 | - only_throw_errors 92 | - overridden_fields 93 | - package_api_docs 94 | - package_names 95 | - package_prefixed_library_names 96 | - parameter_assignments 97 | - prefer_adjacent_string_concatenation 98 | - prefer_asserts_in_initializer_lists 99 | - prefer_asserts_with_message 100 | - prefer_collection_literals 101 | - prefer_conditional_assignment 102 | - prefer_const_constructors 103 | - prefer_const_constructors_in_immutables 104 | - prefer_const_declarations 105 | - prefer_const_literals_to_create_immutables 106 | - prefer_constructors_over_static_methods 107 | - prefer_contains 108 | - prefer_double_quotes 109 | - prefer_equal_for_default_values 110 | - prefer_expression_function_bodies 111 | - prefer_final_fields 112 | - prefer_final_in_for_each 113 | - prefer_final_locals 114 | - prefer_for_elements_to_map_fromIterable 115 | - prefer_foreach 116 | - prefer_function_declarations_over_variables 117 | - prefer_generic_function_type_aliases 118 | - prefer_if_elements_to_conditional_expressions 119 | - prefer_if_null_operators 120 | - prefer_initializing_formals 121 | - prefer_inlined_adds 122 | - prefer_int_literals 123 | - prefer_interpolation_to_compose_strings 124 | - prefer_is_empty 125 | - prefer_is_not_empty 126 | - prefer_is_not_operator 127 | - prefer_iterable_whereType 128 | - prefer_mixin 129 | - prefer_null_aware_operators 130 | - prefer_relative_imports 131 | - prefer_single_quotes 132 | - prefer_spread_collections 133 | - prefer_typing_uninitialized_variables 134 | - prefer_void_to_null 135 | - provide_deprecation_message 136 | - public_member_api_docs 137 | - recursive_getters 138 | - sized_box_for_whitespace 139 | - slash_for_doc_comments 140 | - sort_child_properties_last 141 | - sort_constructors_first 142 | - sort_pub_dependencies 143 | - sort_unnamed_constructors_first 144 | - test_types_in_equals 145 | - throw_in_finally 146 | - tighten_type_of_initializing_formals 147 | - type_annotate_public_apis 148 | - type_init_formals 149 | - unawaited_futures 150 | - unnecessary_await_in_return 151 | - unnecessary_brace_in_string_interps 152 | - unnecessary_const 153 | - unnecessary_final 154 | - unnecessary_getters_setters 155 | - unnecessary_lambdas 156 | - unnecessary_new 157 | - unnecessary_null_aware_assignments 158 | - unnecessary_null_checks 159 | - unnecessary_null_in_if_null_operators 160 | - unnecessary_nullable_for_final_variable_declarations 161 | - unnecessary_overrides 162 | - unnecessary_parenthesis 163 | - unnecessary_raw_strings 164 | - unnecessary_statements 165 | - unnecessary_string_escapes 166 | - unnecessary_string_interpolations 167 | - unnecessary_this 168 | - unrelated_type_equality_checks 169 | - unsafe_html 170 | - use_full_hex_values_for_flutter_colors 171 | - use_function_type_syntax_for_parameters 172 | - use_is_even_rather_than_modulo 173 | - use_key_in_widget_constructors 174 | - use_late_for_private_fields_and_variables 175 | - use_raw_strings 176 | - use_rethrow_when_possible 177 | - use_setters_to_change_properties 178 | - use_string_buffers 179 | - use_to_and_as_if_applicable 180 | - valid_regexps 181 | - void_checks -------------------------------------------------------------------------------- /test/src/cli/cli_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("Verify colored messages", () { 29 | expect("msg".ok, "\x1B[32mmsg"); 30 | expect("msg".nok, "\x1B[31mmsg"); 31 | expect("msg".boring, "\x1B[49mmsg"); 32 | }); 33 | 34 | test("Execute task using main execute entrypoint", () async { 35 | const task = TaskName.gradle; 36 | final context = Context(Directory.systemTemp, {}); 37 | final result = await execute(task, context); 38 | expect(result.contains("Received invalid command"), true); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/src/cli/context_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("Verify parsing get flutter context", () { 29 | final context = 30 | toContextOrNull(Directory.current, ["flutter=3.10.6.macos.x64"]); 31 | expect(context != null, true); 32 | }); 33 | 34 | test("Verify parsing producer init context", () { 35 | final context = toContextOrNull(Directory.current, [ 36 | "bom=2023.1.1.beta", 37 | "flutter=3.10.6.macos.arm64", 38 | ]); 39 | 40 | expect(context != null, true); 41 | expect(context!.taskOptions.isNotEmpty, true); 42 | expect(context.taskOptions[TaskOption.bom], "2023.1.1.beta"); 43 | expect(context.taskOptions[TaskOption.flutter], "3.10.6.macos.arm64"); 44 | }); 45 | 46 | test("When an argument is not a valid option value then context is null", () { 47 | final context = toContextOrNull(Directory.current, [ 48 | "init", 49 | "woot=2023.1.1.beta", 50 | "flutter=3.10.6.macos.arm64", 51 | ]); 52 | 53 | expect(context == null, true, 54 | reason: "context should be null because argument is invalid"); 55 | }); 56 | 57 | test("When get context has more than 1 argument, context is null", () { 58 | final context = toContextOrNull(Directory.current, ["get", "this", "and"]); 59 | 60 | expect(context == null, true, 61 | reason: 62 | "context should be null because get can only have one argument"); 63 | }); 64 | 65 | for (final value in [ 66 | "3.10=6=10", 67 | "'3.10=6=10'", 68 | '"3.10=6=10"', 69 | " 3.10 " 70 | ]) { 71 | test("Verify parsing options", () { 72 | final options = toTaskOptionsOrNull(["flutter=$value"]); 73 | expect(options != null, true, 74 | reason: "options should be parsed successfully"); 75 | final flutter = options![TaskOption.flutter]; 76 | expect(flutter, value.trim(), reason: "value should be stored"); 77 | }); 78 | } 79 | 80 | for (final value in ["", " ", "=", "= ", " = "]) { 81 | test("Verify parsing options fails on missing value", () { 82 | expect(toTaskOptionsOrNull(["flutter$value"]), null); 83 | }); 84 | } 85 | 86 | test("When arguments are null then toContextOrNull has no options", () { 87 | final context = toContextOrNull(Directory.current, []); 88 | expect(context != null, true); 89 | expect(context!.taskOptions.isEmpty, true); 90 | }); 91 | 92 | test("Verify copyWith merges options maps", () { 93 | final map1 = {TaskOption.bom: "da-bom", TaskOption.name: "name"}; 94 | final map2 = {TaskOption.bom: "not-da-bom", TaskOption.klutterui: "union"}; 95 | final context = Context(Directory(""), map1); 96 | final copied = context.copyWith(taskOptions: map2); 97 | final copiedMap = copied.taskOptions; 98 | expect(copiedMap.length, 3); 99 | expect(copiedMap[TaskOption.bom], map2[TaskOption.bom]); 100 | expect(copiedMap[TaskOption.name], map1[TaskOption.name]); 101 | expect(copiedMap[TaskOption.klutterui], map2[TaskOption.klutterui]); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /test/src/cli/option_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "package:klutter/klutter.dart"; 22 | import "package:test/test.dart"; 23 | 24 | void main() { 25 | test("Verify InputException toString", () { 26 | expect(const InputException("splash!").toString(), 27 | "InputException with cause: 'splash!'"); 28 | }); 29 | 30 | test("Verify SquintPubVersion returns input as-is", () { 31 | expect(const SquintPubVersion().convertOrThrow("1.2.3"), "1.2.3"); 32 | }); 33 | 34 | test("Verify KlutteruiPubVersion returns input as-is", () { 35 | expect(const KlutteruiPubVersion().convertOrThrow("1.2.3"), "1.2.3"); 36 | }); 37 | 38 | test("Verify KlutterGradleVersionOption returns input if it is valid", () { 39 | expect(const KlutterGradleVersionOption().convertOrThrow("2025.1.1.beta"), 40 | "2025.1.1.beta"); 41 | }); 42 | 43 | test("Verify IosVersionOption throws exception if input is invalid", () { 44 | expect( 45 | () => const IosVersionOption().convertOrThrow("doubleCheeseBurger"), 46 | throwsA(predicate((e) => 47 | e is InputException && 48 | e.cause == "not a valid ios version: doubleCheeseBurger"))); 49 | }); 50 | 51 | test("Verify IosVersionOption throws exception if ios version is too old", 52 | () { 53 | expect( 54 | () => const IosVersionOption().convertOrThrow("10"), 55 | throwsA(predicate((e) => 56 | e is InputException && 57 | e.cause == 58 | "ios version is too old (min version is $iosVersion): 10"))); 59 | }); 60 | 61 | test("KlutterGradleVersionOption throws exception if input is invalid", () { 62 | expect( 63 | () => const KlutterGradleVersionOption().convertOrThrow("spidey.2099"), 64 | throwsA(predicate((e) => 65 | e is InputException && 66 | e.cause == "not a valid bom version: spidey.2099"))); 67 | }); 68 | 69 | test("PluginNameOption throws exception if input is invalid", () { 70 | expect( 71 | () => const PluginNameOption().convertOrThrow("spidey.2099"), 72 | throwsA(predicate((e) => 73 | e is InputException && 74 | e.cause.contains("pluginName error: Should only contain" 75 | " lowercase alphabetic, numeric and or _ characters" 76 | " and start with an alphabetic character ('my_plugin').")))); 77 | }); 78 | 79 | group("GroupNameOption throws exception if input is not a bool", () { 80 | const option = GroupNameOption(); 81 | void testThrowing(String groupName, String message) { 82 | test("$groupName throws $message", () { 83 | expect( 84 | () => option.convertOrThrow(groupName), 85 | throwsA( 86 | predicate((e) => e is InputException && e.cause == message))); 87 | }); 88 | } 89 | 90 | testThrowing("mac", 91 | "GroupName error: Should contain at least 2 parts ('com.example')."); 92 | testThrowing("mac_.d", 93 | "GroupName error: Characters . and _ can not precede each other."); 94 | testThrowing("9mac.d2", 95 | "GroupName error: Should be lowercase alphabetic separated by dots ('com.example')."); 96 | }); 97 | 98 | test("GroupNameOption throws exception if input is not a bool", () { 99 | expect( 100 | () => const UserInputBooleanOrDefault("foo", false) 101 | .convertOrThrow("vrooooom!"), 102 | throwsA(predicate((e) => 103 | e is InputException && 104 | e.cause == "expected a bool value but received: vrooooom!"))); 105 | }); 106 | 107 | test("UserInputBooleanOrDefault throws exception if input is not a bool", () { 108 | expect( 109 | () => const UserInputBooleanOrDefault("foo", false) 110 | .convertOrThrow("vrooooom!"), 111 | throwsA(predicate((e) => 112 | e is InputException && 113 | e.cause == "expected a bool value but received: vrooooom!"))); 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /test/src/cli/runner_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "package:klutter/klutter.dart"; 22 | import "package:test/test.dart"; 23 | 24 | void main() { 25 | test("Verify argument help returns help text", () async { 26 | final output = await run(["help"]); 27 | expect(output.contains("Usage: kradlew [option=value]"), true, 28 | reason: output); 29 | }); 30 | 31 | test("Verify invalid task is handled", () async { 32 | final output = await run(["blabla"]); 33 | expect(output.contains("received unknown task name"), true, reason: output); 34 | }); 35 | 36 | test("Verify invalid arguments are handled", () async { 37 | final output = await run(["create", "blabla"]); 38 | expect(output.contains("received invalid task options: [blabla]"), true, 39 | reason: output); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /test/src/cli/task_add_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("ConsumerAdd fails when option is not set", () async { 29 | final result = await AddLibrary() 30 | .execute(Context(Directory.current, {})); 31 | expect(result.isOk, false); 32 | expect(result.message, 33 | "unable to run task add because: [missing value for option: lib]"); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/src/cli/task_get_flutter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("GetFlutterSDK downloads latest compatible if version is not set", 29 | () async { 30 | final task = GetFlutterSDK(); 31 | final result = await task.execute(context({TaskOption.dryRun: "true"})); 32 | expect(result.isOk, true); 33 | }); 34 | 35 | test("GetFlutterSDK fails when Flutter SDK is incorrect", () async { 36 | final result = await GetFlutterSDK().execute( 37 | context({TaskOption.flutter: "0.0.0", TaskOption.dryRun: "true"})); 38 | 39 | expect(result.isOk, false); 40 | expect(result.message, 41 | "unable to run task get because: [invalid value for option flutter: invalid flutter version (supported versions are: (3.0.5, 3.3.10, 3.7.12, 3.10.6)): 0.0.0]"); 42 | }); 43 | 44 | test("GetFlutterSDK uses OS from version if present in version String", 45 | () async { 46 | final task = GetFlutterSDK(); 47 | final root = Directory.systemTemp; 48 | root.resolveFile("kradle.env") 49 | ..createSync() 50 | ..writeAsStringSync("cache=${root.absolutePath}"); 51 | final result = await task.execute(context({ 52 | TaskOption.flutter: "3.3.10.linux.x64", 53 | TaskOption.dryRun: "true", 54 | TaskOption.root: root.absolutePath, 55 | })); 56 | expect(result.isOk, true, reason: result.message ?? ""); 57 | }); 58 | 59 | test("Verify skipDownload", () async { 60 | platform = PlatformWrapper() 61 | ..environmentMap = {"GET_FLUTTER_SDK_SKIP": "SKIPPIE"}; 62 | final task = GetFlutterSDK(); 63 | expect(task.skipDownload(true), true); 64 | expect(task.skipDownload(false), true); 65 | platform = PlatformWrapper(); 66 | expect(task.skipDownload(true), true); 67 | expect(task.skipDownload(false), false); 68 | }); 69 | 70 | test("Verify requiresDownload", () async { 71 | final task = GetFlutterSDK(); 72 | final fakeDirectory = Directory("fake"); 73 | final realDirectory = Directory.current; 74 | expect(task.requiresDownload(fakeDirectory, false), true); 75 | expect(task.requiresDownload(fakeDirectory, true), true); 76 | expect(task.requiresDownload(realDirectory, false), false); 77 | expect(task.requiresDownload(realDirectory, true), true); 78 | }); 79 | 80 | test( 81 | "Verify toFlutterDistributionOrThrow throws exception for unsupported platform", 82 | () { 83 | expect( 84 | () => toFlutterDistributionOrThrow( 85 | platformWrapper: UnknownPlatform(), 86 | pathToRoot: 87 | Directory.systemTemp.resolveDirectory("foo").absolutePath, 88 | version: const VerifiedFlutterVersion( 89 | Version(major: 1, minor: 1, patch: 1))), 90 | throwsA(predicate((e) => e is KlutterException))); 91 | }); 92 | } 93 | 94 | Context context(Map options) => 95 | Context(Directory.current, options); 96 | 97 | class UnknownPlatform extends PlatformWrapper { 98 | @override 99 | bool get isWindows => false; 100 | 101 | @override 102 | bool get isMacos => false; 103 | 104 | @override 105 | bool get isLinux => false; 106 | } 107 | -------------------------------------------------------------------------------- /test/src/cli/task_name_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "package:klutter/src/cli/cli.dart"; 22 | import "package:test/test.dart"; 23 | 24 | void main() { 25 | test("Verify TaskNameParser", () { 26 | { 27 | "add": TaskName.add, 28 | " add ": TaskName.add, 29 | "build": TaskName.build, 30 | "clean": TaskName.clean, 31 | "create": TaskName.create, 32 | "get": TaskName.get, 33 | "gradle": TaskName.gradle, 34 | "flutter": TaskName.flutter, 35 | " init": TaskName.init, 36 | "INIT": TaskName.init, 37 | "i n i t": null, 38 | "destroyAllHumans!": null, 39 | null: null, 40 | "": null, 41 | " ": null, 42 | }.forEach((input, taskName) { 43 | expect(input.toTaskNameOrNull, taskName, 44 | reason: "Expected '$input' to be converted to '$taskName' "); 45 | }); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /test/src/cli/task_project_build_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | import "../common/executor_test.dart"; 28 | 29 | void main() { 30 | test("Verify build command executes gradlew clean build", () { 31 | final pathToRoot = 32 | Directory("${Directory.systemTemp.absolute.path}/build_test".normalize) 33 | ..createSync(); 34 | 35 | final executor = FakeExecutor( 36 | expectedPathToWorkingDirectory: pathToRoot.absolutePath, 37 | expectedCommand: 38 | "${pathToRoot.resolveFile("gradlew").absolutePath} clean build -p platform"); 39 | 40 | BuildProject(executor: executor).toBeExecuted(Context(pathToRoot, {}), {}); 41 | 42 | expect(executor.run().stdout, "Test OK!"); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /test/src/cli/task_project_clean_cache_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:meta/meta.dart"; 26 | import "package:test/test.dart"; 27 | 28 | void main() { 29 | Future runTest( 30 | Directory workingDirectory, Directory cachingDirectory, 31 | [CleanCache? cc]) { 32 | final task = cc ?? CleanCache(); 33 | 34 | final context = Context(workingDirectory, {}); 35 | 36 | workingDirectory 37 | .resolveFile("kradle.env") 38 | .writeAsStringSync("cache=${cachingDirectory.absolutePath}"); 39 | 40 | return task.execute(context); 41 | } 42 | 43 | test("Verify clean cache is successful when cache contains nothing", 44 | () async { 45 | final workingDirectory = Directory.systemTemp.resolveDirectory("foo1") 46 | ..maybeCreate; 47 | final cachingDirectory = Directory.systemTemp 48 | .resolveDirectory("foo1/.kradle/cache") 49 | ..maybeCreate; 50 | expect(cachingDirectory.isEmpty, true); 51 | final result = await runTest(workingDirectory, cachingDirectory); 52 | expect(result.isOk, true); 53 | expect(cachingDirectory.isEmpty, true); 54 | }); 55 | 56 | test("Verify clean cache deletes all cache entries", () async { 57 | final workingDirectory = Directory.systemTemp.resolveDirectory("foo1") 58 | ..maybeCreate; 59 | final cachingDirectory = Directory.systemTemp 60 | .resolveDirectory("foo1/.kradle/cache") 61 | ..maybeCreate; 62 | cachingDirectory.resolveDirectory("3.10.6.linux.arm").createSync(); 63 | expect(cachingDirectory.isEmpty, false); 64 | final result = await runTest(workingDirectory, cachingDirectory); 65 | expect(result.isOk, true); 66 | expect(cachingDirectory.isEmpty, true); 67 | final CleanCacheResult ccr = result.output; 68 | expect(ccr.deleted.length, 1); 69 | expect(ccr.notDeletedByError.isEmpty, true); 70 | }); 71 | 72 | test("Verify exceptions are caught when deleting entities", () async { 73 | final workingDirectory = Directory.systemTemp.resolveDirectory("foo2") 74 | ..maybeCreate; 75 | final cachingDirectory = DirectoryStub(); 76 | expect(cachingDirectory.isEmpty, false); 77 | final cacheProvider = CacheProviderStub(); 78 | final cleanCache = CleanCache(cacheProvider); 79 | final result = 80 | await runTest(workingDirectory, cachingDirectory, cleanCache); 81 | expect(result.isOk, true); 82 | expect(cachingDirectory.isEmpty, false); 83 | final CleanCacheResult ccr = result.output; 84 | expect(ccr.deleted.length, 0); 85 | expect(ccr.notDeletedByError.isNotEmpty, true); 86 | expect(ccr.notDeletedByError[DirectoryStub()], 87 | "KlutterException with cause: 'BOOM!'"); 88 | }); 89 | } 90 | 91 | class CacheProviderStub extends CacheProvider { 92 | @override 93 | List getCacheContent( 94 | Context context, 95 | Map options, 96 | ) => 97 | [DirectoryStub()]; 98 | } 99 | 100 | @immutable 101 | class DirectoryStub implements Directory { 102 | @override 103 | bool operator ==(Object other) => true; 104 | 105 | @override 106 | int get hashCode => 1; 107 | 108 | @override 109 | Directory get absolute => DirectoryStub(); 110 | 111 | @override 112 | Future create({bool recursive = false}) { 113 | throw UnimplementedError(); 114 | } 115 | 116 | @override 117 | void createSync({bool recursive = false}) {} 118 | 119 | @override 120 | Future createTemp([String? prefix]) { 121 | throw UnimplementedError(); 122 | } 123 | 124 | @override 125 | Directory createTempSync([String? prefix]) { 126 | throw UnimplementedError(); 127 | } 128 | 129 | @override 130 | Future delete({bool recursive = false}) { 131 | throw const KlutterException("BOOM!"); 132 | } 133 | 134 | @override 135 | void deleteSync({bool recursive = false}) { 136 | throw const KlutterException("BOOM!"); 137 | } 138 | 139 | @override 140 | Future exists() => Future.value(true); 141 | 142 | @override 143 | bool existsSync() => true; 144 | 145 | @override 146 | bool get isAbsolute => throw UnimplementedError(); 147 | 148 | @override 149 | Stream list( 150 | {bool recursive = false, bool followLinks = true}) => 151 | Stream.value(DirectoryStub()); 152 | 153 | @override 154 | List listSync( 155 | {bool recursive = false, bool followLinks = true}) => 156 | [DirectoryStub()]; 157 | 158 | @override 159 | Directory get parent => throw UnimplementedError(); 160 | 161 | @override 162 | String get path => Directory.systemTemp.resolveDirectory("stub").absolutePath; 163 | 164 | @override 165 | Future rename(String newPath) { 166 | throw UnimplementedError(); 167 | } 168 | 169 | @override 170 | Directory renameSync(String newPath) { 171 | throw UnimplementedError(); 172 | } 173 | 174 | @override 175 | Future resolveSymbolicLinks() { 176 | throw UnimplementedError(); 177 | } 178 | 179 | @override 180 | String resolveSymbolicLinksSync() { 181 | throw UnimplementedError(); 182 | } 183 | 184 | @override 185 | Future stat() { 186 | throw UnimplementedError(); 187 | } 188 | 189 | @override 190 | FileStat statSync() { 191 | throw UnimplementedError(); 192 | } 193 | 194 | @override 195 | Uri get uri => throw UnimplementedError(); 196 | 197 | @override 198 | Stream watch( 199 | {int events = FileSystemEvent.all, bool recursive = false}) { 200 | throw UnimplementedError(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /test/src/cli/task_project_create_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("Verify a git path is converted to correct yaml dependency", () async { 29 | const git = "https://github.com/foo.git@develop"; 30 | const expected = "| foobar:\n" 31 | "| git:\n" 32 | "| url: https://github.com/foo.git\n" 33 | "| ref: develop\n" 34 | ""; 35 | final yaml = toDependencyNotation(git, "foobar"); 36 | expect(yaml, expected); 37 | }); 38 | 39 | test("Verify a version path is converted to correct yaml dependency", 40 | () async { 41 | const version = "2.4.8"; 42 | const expected = "| foobar: ^2.4.8"; 43 | final yaml = toDependencyNotation(version, "foobar"); 44 | expect(yaml, expected); 45 | }); 46 | 47 | test("Verify an exception is thrown if the dependency notation is invalid", 48 | () async { 49 | const notation = "1.2.beta"; 50 | expect( 51 | () => toDependencyNotation(notation, "foobar"), 52 | throwsA(predicate((e) => 53 | e is KlutterException && 54 | e.cause == "invalid dependency notations: 1.2.beta"))); 55 | }); 56 | 57 | test("Verify an exception is thrown if flutter sdk is not found", () async { 58 | final getFlutterTask = NoFlutterSDK(); 59 | final task = CreateProject(getFlutterSDK: getFlutterTask); 60 | final result = await task.execute(Context(Directory.systemTemp, {})); 61 | expect(result.isOk, false); 62 | expect(result.message, "BOOM!"); 63 | }); 64 | 65 | test("Verify flutter option is used correctly", () async { 66 | const version = "3.0.5.macos.arm64"; 67 | final getFlutterTask = FlutterFixedVersion(); 68 | final task = CreateProject(getFlutterSDK: getFlutterTask); 69 | final result = await task 70 | .execute(Context(Directory.systemTemp, {TaskOption.flutter: version})); 71 | expect(result.isOk, false); 72 | expect(result.message, version); 73 | }); 74 | } 75 | 76 | class NoFlutterSDK extends GetFlutterSDK { 77 | @override 78 | Future executeOrThrow(Context context) async { 79 | throw const KlutterException("BOOM!"); 80 | } 81 | } 82 | 83 | class FlutterFixedVersion extends GetFlutterSDK { 84 | @override 85 | Future executeOrThrow(Context context) async { 86 | throw KlutterException(context.taskOptions[TaskOption.flutter] ?? "--"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/src/cli/task_project_init_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("Verify init in consumer project skips producer init", () async { 29 | final pathToRoot = 30 | Directory("${Directory.systemTemp.absolute.path}/build_test".normalize) 31 | ..createSync(); 32 | 33 | final project = await createFlutterProjectOrThrow( 34 | executor: Executor(), 35 | pathToFlutter: "flutter", 36 | pathToRoot: pathToRoot.absolutePath, 37 | name: "my_project", 38 | group: "my.org", 39 | ); 40 | 41 | final pathToConsumer = project.resolveDirectory("example"); 42 | final task = ProjectInit(); 43 | final context = Context(pathToConsumer, {}); 44 | final result = await task.execute(context); 45 | expect(result.isOk, true, reason: result.message); 46 | final file = pathToConsumer.resolveFile("/lib/main.dart"); 47 | var reason = "example/lib/main.dart file should exist"; 48 | expect(file.existsSync(), true, reason: reason); 49 | final registry = pathToConsumer.resolveFile(".klutter-plugins"); 50 | reason = "klutter-plugins file should be created"; 51 | expect(registry.existsSync(), true, reason: reason); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/src/cli/task_service_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/src/cli/cli.dart"; 24 | import "package:klutter/src/common/common.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | final pathToRoot = Directory("${Directory.systemTemp.path}/tst1".normalize) 29 | ..maybeCreate; 30 | 31 | final service = TaskService(); 32 | 33 | group("Test allTasks", () { 34 | test("Verify allTasks returns 7 tasks", () { 35 | final tasks = service.allTasks(); 36 | 37 | expect(tasks.length, 6); 38 | 39 | expect( 40 | tasks.getTask(TaskName.add).toString(), 41 | "add\n lib (Required) name of the library to add.\n" 42 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n", 43 | ); 44 | 45 | expect( 46 | tasks.getTask(TaskName.init).toString(), 47 | "init\n bom (Optional) klutter gradle version. Defaults to '$klutterGradleVersion'.\n flutter (Optional) flutter sdk version in format major.minor.patch. Defaults to '3.10.6'.\n root (Optional) klutter project root directory. Defaults to 'current working directory'.\n ios (Optional) ios version. Defaults to '13.0'.\n", 48 | ); 49 | }); 50 | 51 | test("Verify exception is thrown if duplicate tasks are found", () { 52 | final duplicateTasks = [ 53 | ProjectInit(), 54 | ProjectInit(), 55 | ]; 56 | 57 | expect( 58 | () => service.allTasks(duplicateTasks), 59 | throwsA(predicate((e) => 60 | e is KlutterException && 61 | e.cause.startsWith("TaskService configuration failure.") && 62 | e.cause.contains("Invalid or duplicate TaskName encountered.")))); 63 | }); 64 | }); 65 | 66 | test("Verify displayKradlewHelpText output", () { 67 | final help = service.displayKradlewHelpText; 68 | const expected = "Manage your klutter project." 69 | "\n" 70 | "\n" 71 | "Usage: kradlew [option=value]\n" 72 | "\n" 73 | "add\n" 74 | " lib (Required) name of the library to add.\n" 75 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 76 | "\n" 77 | "init\n" 78 | " bom (Optional) klutter gradle version. Defaults to '$klutterGradleVersion'.\n" 79 | " flutter (Optional) flutter sdk version in format major.minor.patch. Defaults to '3.10.6'.\n" 80 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 81 | " ios (Optional) ios version. Defaults to '13.0'.\n" 82 | "\n" 83 | "get\n" 84 | " flutter (Optional) flutter sdk version in format major.minor.patch. Defaults to '3.10.6'.\n" 85 | " overwrite (Optional) overwrite existing distribution when found. Defaults to 'false'.\n" 86 | " dryRun (Optional) skip downloading of libraries. Defaults to 'false'.\n" 87 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 88 | "\n" 89 | "create\n" 90 | " name (Optional) plugin name. Defaults to 'my_plugin'.\n" 91 | " group (Optional) plugin group name. Defaults to 'dev.buijs.klutter.example'.\n" 92 | " flutter (Optional) flutter sdk version in format major.minor.patch. Defaults to '3.10.6'.\n" 93 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 94 | " klutter (Optional) klutter pub version. Defaults to '$klutterPubVersion'.\n" 95 | " klutterui (Optional) klutter_ui pub version. Defaults to '1.1.0'.\n" 96 | " bom (Optional) klutter gradle version. Defaults to '$klutterGradleVersion'.\n" 97 | " squint (Optional) squint_json pub version. Defaults to '0.1.2'.\n" 98 | "\n" 99 | "build\n" 100 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 101 | "\n" 102 | "clean\n" 103 | " root (Optional) klutter project root directory. Defaults to 'current working directory'.\n" 104 | "\n" 105 | ""; 106 | expect(help, expected); 107 | }); 108 | tearDownAll(() => pathToRoot.deleteSync(recursive: true)); 109 | } 110 | 111 | extension on Set { 112 | Task getTask(TaskName taskName) => firstWhere((task) { 113 | return task.taskName == taskName; 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /test/src/cli/task_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:klutter/src/cli/context.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | test("When a task fails with a KlutterException, it is caught", () async { 29 | final result = 30 | await _ExplodingTask().execute(Context(Directory.systemTemp, {})); 31 | expect(result.isOk, false); 32 | expect(result.message, "BOOM!"); 33 | }); 34 | 35 | test("An exception is thrown when an option value is invalid", () async { 36 | final result = 37 | await _InvalidTask().execute(Context(Directory.systemTemp, {})); 38 | expect(result.isOk, false); 39 | expect(result.message, "unsupported value: Instance of 'FakeInput'"); 40 | }); 41 | 42 | test("An exception is thrown when unsupported options are present", () async { 43 | final result = await CreateProject().execute( 44 | Context(Directory.systemTemp, {TaskOption.overwrite: "false"})); 45 | expect(result.isOk, false); 46 | expect(result.message, 47 | "unable to run task create because: [option not supported for task create: overwrite]"); 48 | }); 49 | 50 | test("Verify toTaskOptionOrNull for valid options", () async { 51 | for (final value in TaskOption.values) { 52 | expect(value.name.toTaskOptionOrNull, value, reason: "roundtrip $value"); 53 | } 54 | }); 55 | } 56 | 57 | class _ExplodingTask extends Task { 58 | _ExplodingTask() : super(TaskName.add, {}); 59 | 60 | @override 61 | Future toBeExecuted(Context context, Map options) { 62 | throw const KlutterException("BOOM!"); 63 | } 64 | } 65 | 66 | class _InvalidTask extends Task { 67 | _InvalidTask() : super(TaskName.add, {TaskOption.group: FakeInput()}); 68 | 69 | @override 70 | Future toBeExecuted( 71 | Context context, Map options) { 72 | return Future.value(""); 73 | } 74 | } 75 | 76 | class FakeInput extends Input { 77 | @override 78 | String convertOrThrow(String value) => "bar"; 79 | 80 | @override 81 | String get description => "foo"; 82 | } 83 | -------------------------------------------------------------------------------- /test/src/common/config_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "package:klutter/klutter.dart"; 22 | import "package:test/test.dart"; 23 | 24 | void main() { 25 | test("Valid Flutter versions are returned as VerifiedFlutterVersion", () { 26 | for (final version in supportedFlutterVersions.keys) { 27 | expect(version.verifyFlutterVersion != null, true, 28 | reason: "Version should be valid: $version"); 29 | expect("$version.windows.x64".verifyFlutterVersion != null, true, 30 | reason: "Version should be valid: $version.windows.x64"); 31 | expect("$version.macos.x64".verifyFlutterVersion != null, true, 32 | reason: "Version should be valid: $version.macos.x64"); 33 | expect("$version.linux.x64".verifyFlutterVersion != null, true, 34 | reason: "Version should be valid: $version.linux.x64"); 35 | expect("$version.windows.arm64".verifyFlutterVersion != null, true, 36 | reason: "Version should be valid: $version.windows.arm64"); 37 | expect("$version.macos.arm64".verifyFlutterVersion != null, true, 38 | reason: "Version should be valid: $version.macos.arm64"); 39 | expect("$version.linux.arm64".verifyFlutterVersion != null, true, 40 | reason: "Version should be valid: $version.linux.arm64"); 41 | } 42 | }); 43 | 44 | test("Verify VerifiedFlutterVersion toString", () { 45 | const version = VerifiedFlutterVersion( 46 | Version(major: 3, minor: 10, patch: 6), 47 | os: OperatingSystem.windows, 48 | arch: Architecture.arm64); 49 | 50 | expect(version.toString(), 51 | "VerifiedFlutterVersion(Version(3.10.6), OperatingSystem.windows, Architecture.arm64)", 52 | reason: "Version should be valid: $version"); 53 | }); 54 | 55 | test("Invalid Flutter versions are returned as null", () { 56 | expect("thisIsNotAFlutterVersion".verifyFlutterVersion == null, true); 57 | }); 58 | 59 | group("Verify version sort order is descending by default", () { 60 | void testSorting(String a, String b, List expected) { 61 | test("$a > $b", () { 62 | final sortedList = [a.toVersion, b.toVersion]..sort(); 63 | expect(sortedList.map((e) => e.prettyPrint).toList(), expected); 64 | }); 65 | } 66 | 67 | testSorting("1.0.0", "1.0.0", ["1.0.0", "1.0.0"]); 68 | testSorting("1.0.0", "1.1.0", ["1.1.0", "1.0.0"]); 69 | testSorting("1.0.0", "1.0.1", ["1.0.1", "1.0.0"]); 70 | testSorting("0.1.0", "0.0.1", ["0.1.0", "0.0.1"]); 71 | }); 72 | 73 | test("Version factory method throws exception on invalid value", () { 74 | expect(() => Version.fromString("not a version"), 75 | throwsA(predicate((e) => e is KlutterException))); 76 | }); 77 | 78 | test("Verify Version comparator", () { 79 | const v1 = Version(major: 1, minor: 1, patch: 1); 80 | const v2 = Version(major: 2, minor: 1, patch: 1); 81 | const v3 = Version(major: 2, minor: 2, patch: 1); 82 | const v4 = Version(major: 2, minor: 2, patch: 2); 83 | const v5 = Version(major: 2, minor: 2, patch: 2); 84 | expect(v1.compareTo(v2), 1); 85 | expect(v2.compareTo(v1), -1); 86 | expect(v2.compareTo(v3), 1); 87 | expect(v3.compareTo(v2), -1); 88 | expect(v3.compareTo(v4), 1); 89 | expect(v4.compareTo(v3), -1); 90 | expect(v4.compareTo(v5), 0); 91 | }); 92 | 93 | test("Version factory method throws exception on invalid value", () { 94 | expect(() => Version.fromString("not a version"), 95 | throwsA(predicate((e) => e is KlutterException))); 96 | }); 97 | 98 | test("Verify Version toString", () { 99 | const version = Version(major: 3, minor: 10, patch: 6); 100 | final printed = version.toString(); 101 | expect(printed, "Version(3.10.6)"); 102 | }); 103 | } 104 | 105 | extension on String { 106 | Version get toVersion { 107 | final splitted = split("."); 108 | return Version( 109 | major: int.parse(splitted[0]), 110 | minor: int.parse(splitted[1]), 111 | patch: int.parse(splitted[2]), 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/src/common/exception_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "package:klutter/src/common/exception.dart"; 22 | import "package:test/test.dart"; 23 | 24 | void main() { 25 | test("Verify exception toString returns a readable message", () { 26 | expect( 27 | () => throw const KlutterException("BOOM!"), 28 | throwsA(predicate((e) => 29 | e is KlutterException && 30 | e.toString() == "KlutterException with cause: 'BOOM!'"))); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /test/src/common/executor_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | import "package:klutter/klutter.dart"; 23 | import "package:test/test.dart"; 24 | 25 | /// Wrapper for using the commandline. 26 | class FakeExecutor extends Executor { 27 | FakeExecutor( 28 | {required this.expectedPathToWorkingDirectory, 29 | required this.expectedCommand}); 30 | 31 | final String expectedPathToWorkingDirectory; 32 | 33 | final String expectedCommand; 34 | 35 | @override 36 | ProcessResult run() { 37 | final actualPathToWorkingDirectory = super.workingDirectory?.absolutePath; 38 | final actualCommand = 39 | """${super.executable} ${super.arguments.join(" ")}"""; 40 | _assert(expectedPathToWorkingDirectory, actualPathToWorkingDirectory); 41 | _assert(expectedCommand, actualCommand); 42 | return ProcessResult(1234, 0, "Test OK!", ""); 43 | } 44 | 45 | void _assert(String expected, String? actual) { 46 | assert(expected == actual, 47 | "Test NOK because values do not match:\nExpected: $expected\nActual: $actual"); 48 | } 49 | } 50 | 51 | void main() { 52 | test("Verify exception is thrown when executable is not set", () { 53 | expect( 54 | () => Executor().run(), 55 | throwsA(predicate((e) => 56 | e is KlutterException && 57 | e.cause.startsWith("Executor field 'executable' is null")))); 58 | }); 59 | 60 | test("Verify exception is thrown when workingDirectory is not set", () { 61 | expect(() { 62 | Executor() 63 | ..executable = "dart" 64 | ..run(); 65 | }, 66 | throwsA(predicate((e) => 67 | e is KlutterException && 68 | e.cause.startsWith("Executor field 'workingDirectory' is null")))); 69 | }); 70 | 71 | test("Verify command is run successfully", () { 72 | final exec = Executor() 73 | ..executable = "dart" 74 | ..workingDirectory = Directory.current 75 | ..arguments = ["--version"]; 76 | 77 | final version = exec.run(); 78 | 79 | expect(version.stdout.toString().contains("Dart SDK version: "), true, 80 | reason: "stdout should contain the installed dart version"); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /test/src/common/project_kradle_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2024 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | import "dart:math"; 23 | import "package:klutter/klutter.dart"; 24 | import "package:test/test.dart"; 25 | 26 | final _platform = FakePlatformWrapper(); 27 | 28 | Directory get _newProjectFolder => Directory.systemTemp 29 | .resolveDirectory("project_test${Random().nextInt(999999)}") 30 | ..createSync(); 31 | 32 | void setPlatform(String os, String homeKey, String homeValue) { 33 | _platform 34 | ..environment = {homeKey: homeValue} 35 | ..operatingSystem = os; 36 | } 37 | 38 | void setPlatformMacos(Directory rootDirectory) { 39 | setPlatform("macos", "HOME", rootDirectory.absolutePath); 40 | } 41 | 42 | void setPlatformLinux(Directory rootDirectory) { 43 | setPlatform("linux", "HOME", rootDirectory.absolutePath); 44 | } 45 | 46 | void setPlatformWindows(Directory rootDirectory) { 47 | setPlatform("windows", "USERPROFILE", rootDirectory.absolutePath); 48 | } 49 | 50 | void createCacheFolder(Directory rootDirectory) { 51 | rootDirectory.resolveDirectory(".kradle/cache".normalize).maybeCreate; 52 | } 53 | 54 | void createKradleEnv(Directory rootDirectory, {String? contents}) { 55 | rootDirectory.resolveFile("kradle.env") 56 | ..createSync() 57 | ..writeAsStringSync(contents ?? ""); 58 | } 59 | 60 | void main() { 61 | setUpAll(() { 62 | platform = _platform; 63 | }); 64 | 65 | group("Default cache is set to user.home/.kradle/cache", () { 66 | void testPlatform(void Function(Directory root) init, String platform) { 67 | test(platform, () { 68 | final rootDirectory = _newProjectFolder; 69 | init(rootDirectory); 70 | createCacheFolder(rootDirectory); 71 | final actual = rootDirectory.kradleCache.absolutePath; 72 | final expected = 73 | "${rootDirectory.absolutePath}/.kradle/cache".normalize; 74 | expect(actual, expected); 75 | }); 76 | } 77 | 78 | testPlatform(setPlatformMacos, "macos"); 79 | testPlatform(setPlatformLinux, "linux"); 80 | testPlatform(setPlatformWindows, "windows"); 81 | }); 82 | 83 | group("Exception is thrown without kradle.env and user.home", () { 84 | void testPlatform(String platform, String expected) { 85 | test("$platform without user.home", () { 86 | setPlatform(platform, "", ""); 87 | expect( 88 | () => _newProjectFolder.kradleCache, 89 | throwsA(predicate( 90 | (e) => e is KlutterException && e.cause.contains(expected)))); 91 | }); 92 | } 93 | 94 | testPlatform("macos", "environment variable 'HOME' is not defined"); 95 | testPlatform("linux", "environment variable 'HOME' is not defined"); 96 | testPlatform("windows", "environment variable 'USERPROFILE'"); 97 | testPlatform("swodniw", "method 'userHome' is not supported on swodniw"); 98 | }); 99 | 100 | test( 101 | "When kradle.env does exist but does not contain cache property then kradleCache is set to HOME/.kradle/cache", 102 | () { 103 | final rootDirectory = _newProjectFolder; 104 | setPlatformMacos(rootDirectory); 105 | createKradleEnv(rootDirectory); 106 | createCacheFolder(rootDirectory); 107 | final actual = rootDirectory.kradleCache.absolutePath; 108 | final expected = "${rootDirectory.absolutePath}/.kradle/cache".normalize; 109 | expect(actual, expected); 110 | }); 111 | 112 | group( 113 | "When kradle.env does exist but does not contain cache property and user.home is not defined then an exception is thrown", 114 | () { 115 | void testPlatform(String platform, String expected) { 116 | test(platform, () { 117 | final rootDirectory = _newProjectFolder; 118 | createKradleEnv(rootDirectory); 119 | setPlatform(platform, "", ""); 120 | expect( 121 | () => rootDirectory.kradleCache, 122 | throwsA(predicate( 123 | (e) => e is KlutterException && e.cause.contains(expected)))); 124 | }); 125 | } 126 | 127 | testPlatform("macos", "environment variable 'HOME' is not defined"); 128 | testPlatform("linux", "environment variable 'HOME' is not defined"); 129 | testPlatform("windows", "environment variable 'USERPROFILE'"); 130 | testPlatform("swodniw", "method 'userHome' is not supported on swodniw"); 131 | }); 132 | 133 | group( 134 | "When kradle.env contains cache property which has user.home variable but user.home is not defined then an exception is thrown", 135 | () { 136 | void testPlatform(String platform, String expected) { 137 | test(platform, () { 138 | final rootDirectory = _newProjectFolder; 139 | 140 | createKradleEnv( 141 | rootDirectory, 142 | contents: "cache={{system.user.home}}/.kradle", 143 | ); 144 | 145 | setPlatform(platform, "", ""); 146 | 147 | expect(() => rootDirectory.kradleCache, throwsA(predicate((e) { 148 | return e is KlutterException && 149 | e.cause.contains(expected) && 150 | e.cause.contains( 151 | "Unable to determine kradle cache directory, because " 152 | "property 'cache' in ${rootDirectory.resolveFile("kradle.env").absolutePath} " 153 | "contains system.user.home variable " 154 | "and $expected. Fix this issue by " 155 | "replacing $kradleEnvPropertyUserHome variable with " 156 | "an absolute path in "); 157 | }))); 158 | }); 159 | } 160 | 161 | testPlatform("macos", "environment variable 'HOME' is not defined"); 162 | testPlatform("linux", "environment variable 'HOME' is not defined"); 163 | testPlatform( 164 | "windows", "environment variable 'USERPROFILE' is not defined"); 165 | testPlatform("swodniw", "method 'userHome' is not supported on swodniw"); 166 | }); 167 | 168 | group( 169 | "When kradle.env contains cache property which has user.home variable then it is successfully replaced", 170 | () { 171 | void testPlatform(void Function(Directory root) init, String platform) { 172 | test(platform, () { 173 | final rootDirectory = _newProjectFolder; 174 | init(rootDirectory); 175 | createCacheFolder(rootDirectory); 176 | 177 | createKradleEnv( 178 | rootDirectory, 179 | contents: "cache={{system.user.home}}/.kradle/cache", 180 | ); 181 | 182 | final actual = rootDirectory.kradleCache.absolutePath; 183 | final expected = 184 | "${rootDirectory.absolutePath}/.kradle/cache".normalize; 185 | expect(actual, expected); 186 | }); 187 | } 188 | 189 | testPlatform(setPlatformMacos, "macos"); 190 | testPlatform(setPlatformLinux, "linux"); 191 | testPlatform(setPlatformWindows, "windows"); 192 | }); 193 | 194 | test( 195 | "When kradle.env contains cache property without user.home variable then it is used as-is", 196 | () { 197 | final rootDirectory = _newProjectFolder; 198 | setPlatformMacos(rootDirectory); 199 | createCacheFolder(rootDirectory); 200 | createKradleEnv( 201 | rootDirectory, 202 | contents: "cache=${rootDirectory.absolutePath}/.kradle/cache", 203 | ); 204 | 205 | final actual = rootDirectory.kradleCache.absolutePath; 206 | final expected = "${rootDirectory.absolutePath}/.kradle/cache".normalize; 207 | expect(actual, expected); 208 | }); 209 | 210 | test( 211 | "When kradle.env contains cache property and the directory is not found then it is created", 212 | () { 213 | final dotKradleDirectory = Directory.systemTemp.resolveDirectory(".kradle") 214 | ..createSync(); 215 | final cacheDirectory = 216 | Directory("${dotKradleDirectory.absolutePath}/cache".normalize); 217 | final rootDirectory = _newProjectFolder; 218 | setPlatformMacos(rootDirectory); 219 | createCacheFolder(rootDirectory); 220 | createKradleEnv( 221 | rootDirectory, 222 | contents: "cache=${cacheDirectory.absolutePath}", 223 | ); 224 | 225 | if (cacheDirectory.existsSync()) { 226 | cacheDirectory.deleteSync(); 227 | } 228 | 229 | expect(cacheDirectory.existsSync(), false); 230 | 231 | // when 232 | rootDirectory.kradleCache; 233 | 234 | // then 235 | expect(cacheDirectory.existsSync(), true); 236 | }); 237 | 238 | tearDownAll(() { 239 | platform = PlatformWrapper(); 240 | }); 241 | } 242 | 243 | class FakePlatformWrapper extends PlatformWrapper { 244 | String _os = Platform.operatingSystem; 245 | 246 | Map _env = Platform.environment; 247 | 248 | @override 249 | String get operatingSystem => _os; 250 | 251 | set operatingSystem(String value) { 252 | _os = value; 253 | } 254 | 255 | @override 256 | Map get environment => _env; 257 | 258 | set environment(Map value) { 259 | _env = value; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /test/src/common/utilities_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:test/test.dart"; 25 | 26 | void main() { 27 | test("Verify verifyExists throws exception if File does not exist", () { 28 | expect( 29 | () => File("BLABLA").verifyFileExists, 30 | throwsA(predicate((e) => 31 | e is KlutterException && 32 | e.cause.startsWith("Path does not exist")))); 33 | }); 34 | 35 | test( 36 | "Verify verifyDirectoryExists throws exception if Directory does not exist", 37 | () { 38 | expect( 39 | () => Directory("BLABLA").verifyDirectoryExists, 40 | throwsA(predicate((e) => 41 | e is KlutterException && 42 | e.cause.startsWith("Path does not exist")))); 43 | }); 44 | 45 | test("Verify _substitute correctly normalizes a path", () { 46 | expect( 47 | File("foo/bar/res/../../pikachu") 48 | .normalizeToFile 49 | .path 50 | .endsWith("foo${Platform.pathSeparator}pikachu"), 51 | true); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/src/producer/gradle_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/src/common/common.dart"; 24 | import "package:klutter/src/producer/gradle.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | final s = Platform.pathSeparator; 29 | 30 | test("Verify exception is thrown if root does not exist", () { 31 | expect( 32 | () => Gradle("fake", Directory.systemTemp).copyToRoot, 33 | throwsA(predicate((e) => 34 | e is KlutterException && 35 | e.cause.startsWith("Path does not exist:") && 36 | e.cause.endsWith("fake")))); 37 | }); 38 | 39 | test("Verify Gradle files are copied to the root folder", () async { 40 | final root = Directory("${Directory.systemTemp.path}${s}gradle10") 41 | ..createSync(recursive: true); 42 | final resources = 43 | Directory("${Directory.systemTemp.path}${s}gradle10resources") 44 | ..createSync(recursive: true); 45 | resources.resolveFile("gradle.properties").createSync(); 46 | resources.resolveFile("gradle-wrapper.jar").createSync(); 47 | resources.resolveFile("gradle-wrapper.properties").createSync(); 48 | resources.resolveFile("gradlew").createSync(); 49 | resources.resolveFile("gradlew.bat").createSync(); 50 | 51 | await Gradle(root.normalizeToDirectory.absolutePath, resources).copyToRoot; 52 | 53 | final properties = File("${root.path}/gradle.properties").normalizeToFile; 54 | final wrapperJar = 55 | File("${root.path}/gradle/wrapper/gradle-wrapper.jar").normalizeToFile; 56 | final wrapperProperties = 57 | File("${root.path}/gradle/wrapper/gradle-wrapper.properties") 58 | .normalizeToFile; 59 | final wrapperSh = File("${root.path}/gradlew").normalizeToFile; 60 | final wrapperBat = File("${root.path}/gradlew.bat").normalizeToFile; 61 | expect(properties.existsSync(), true, 62 | reason: "gradle.properties should exist"); 63 | expect(wrapperJar.existsSync(), true, 64 | reason: "gradle-wrapper.jar should exist"); 65 | expect(wrapperProperties.existsSync(), true, 66 | reason: "${wrapperProperties.absolutePath} should exist"); 67 | expect(wrapperSh.existsSync(), true, 68 | reason: "${wrapperSh.absolutePath} should exist"); 69 | expect(wrapperBat.existsSync(), true, 70 | reason: "${wrapperBat.absolutePath} should exist"); 71 | 72 | root.deleteSync(recursive: true); 73 | }); 74 | 75 | test("Verify exception is thrown if root does not exist", () { 76 | expect( 77 | () => Gradle("fake", Directory.systemTemp).copyToAndroid, 78 | throwsA(predicate((e) => 79 | e is KlutterException && 80 | e.cause.startsWith("Path does not exist:") && 81 | e.cause.endsWith("fake")))); 82 | }); 83 | 84 | test("Verify exception is thrown if root/android does not exist", () { 85 | final root = Directory("${Directory.systemTemp.path}${s}gradle20") 86 | ..createSync(recursive: true); 87 | 88 | expect( 89 | () => Gradle(root.path, Directory.systemTemp).copyToAndroid, 90 | throwsA(predicate((e) => 91 | e is KlutterException && 92 | e.cause.startsWith("Path does not exist:")))); 93 | 94 | root.deleteSync(recursive: true); 95 | }); 96 | 97 | test("Verify Gradle files are copied to the root/android folder", () async { 98 | final root = Directory("${Directory.systemTemp.path}${s}gradle30") 99 | ..createSync(recursive: true); 100 | 101 | final android = Directory("${root.path}/android".normalize) 102 | ..createSync(recursive: true); 103 | final resources = 104 | Directory("${Directory.systemTemp.path}${s}gradle30resources") 105 | ..createSync(recursive: true); 106 | resources.resolveFile("gradle.properties").createSync(); 107 | resources.resolveFile("gradle-wrapper.jar").createSync(); 108 | resources.resolveFile("gradle-wrapper.properties").createSync(); 109 | resources.resolveFile("gradlew").createSync(); 110 | resources.resolveFile("gradlew.bat").createSync(); 111 | await Gradle(root.path, resources).copyToAndroid; 112 | 113 | final properties = 114 | File("${android.path}/gradle.properties").normalizeToFile; 115 | final wrapperJar = File("${android.path}/gradle/wrapper/gradle-wrapper.jar") 116 | .normalizeToFile; 117 | final wrapperProperties = 118 | File("${android.path}/gradle/wrapper/gradle-wrapper.properties") 119 | .normalizeToFile; 120 | final warpperSh = File("${android.path}/gradlew").normalizeToFile; 121 | final wrapperBat = File("${android.path}/gradlew.bat").normalizeToFile; 122 | 123 | expect(properties.existsSync(), true, 124 | reason: "gradle.properties should exist"); 125 | expect(wrapperJar.existsSync(), true, 126 | reason: "gradle-wrapper.jar should exist"); 127 | expect(wrapperProperties.existsSync(), true, 128 | reason: "gradle-wrapper.properties should exist"); 129 | expect(warpperSh.existsSync(), true, reason: "gradlew should exist"); 130 | expect(wrapperBat.existsSync(), true, reason: "gradlew.bat should exist"); 131 | 132 | root.deleteSync(recursive: true); 133 | }); 134 | } 135 | -------------------------------------------------------------------------------- /test/src/producer/ios_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/klutter.dart"; 24 | import "package:test/test.dart"; 25 | 26 | void main() { 27 | final temp = Directory.systemTemp..createTempSync(); 28 | 29 | test("Verify exception is thrown if podspec file does not exist", () { 30 | final folder = Directory("${temp.absolutePath}/ios_test1".normalize) 31 | ..createSync(); 32 | 33 | expect( 34 | () => addFrameworkAndSetIosVersionInPodspec( 35 | pluginName: "some_plugin", 36 | pathToIos: folder.absolutePath, 37 | iosVersion: 15), 38 | throwsA(predicate((e) => 39 | e is KlutterException && 40 | e.cause.startsWith("Missing podspec file")))); 41 | }); 42 | 43 | test("Verify exception is thrown if addFramework fails", () { 44 | final folder = Directory("${temp.absolutePath}/ios_test2".normalize) 45 | ..createSync(); 46 | 47 | File("${folder.absolutePath}/some_plugin.podspec").createSync(); 48 | 49 | expect( 50 | () => addFrameworkAndSetIosVersionInPodspec( 51 | pluginName: "some_plugin", 52 | pathToIos: folder.absolutePath, 53 | iosVersion: 15), 54 | throwsA(predicate((e) => 55 | e is KlutterException && 56 | e.cause.startsWith( 57 | "Failed to add Platform.framework to ios folder.")))); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /test/src/producer/kradle_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/src/common/common.dart"; 24 | import "package:klutter/src/producer/kradle.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | final s = Platform.pathSeparator; 29 | 30 | test("Verify exception is thrown if root does not exist", () { 31 | expect( 32 | () => Kradle("fake", Directory.systemTemp).copyToRoot, 33 | throwsA(predicate((e) => 34 | e is KlutterException && 35 | e.cause.startsWith("Path does not exist:") && 36 | e.cause.endsWith("fake")))); 37 | }); 38 | 39 | test("Verify Kradle files are copied to the root folder", () async { 40 | final root = Directory("${Directory.systemTemp.path}${s}kradle10") 41 | ..createSync(recursive: true); 42 | final resources = 43 | Directory("${Directory.systemTemp.path}${s}kradle10resources") 44 | ..createSync(recursive: true); 45 | resources.resolveFile("kradle.env").createSync(); 46 | resources.resolveFile("kradle.yaml").createSync(); 47 | await Kradle(root.normalizeToDirectory.absolutePath, resources).copyToRoot; 48 | final env = File("${root.path}/kradle.env").normalizeToFile; 49 | final yaml = File("${root.path}/kradle.yaml").normalizeToFile; 50 | expect(env.existsSync(), true, reason: "kradle.env should exist"); 51 | expect(yaml.existsSync(), true, reason: "kradle.yaml should exist"); 52 | root.deleteSync(recursive: true); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /test/src/producer/project_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | import "dart:io"; 22 | 23 | import "package:klutter/src/common/common.dart"; 24 | import "package:klutter/src/producer/project.dart"; 25 | import "package:test/test.dart"; 26 | 27 | void main() { 28 | const pluginName = "impressive_dependency"; 29 | 30 | test("Verify example/lib/main.dart is created if it does not exist", () { 31 | final example = Directory("${Directory.systemTemp.path}/wem1".normalize) 32 | ..createSync(recursive: true); 33 | 34 | final mainDart = File("${example.path}/lib/main.dart".normalize); 35 | 36 | writeExampleMainDartFile( 37 | pathToExample: example.path, 38 | pluginName: pluginName, 39 | ); 40 | 41 | expect( 42 | mainDart.readAsStringSync().replaceAll(" ", ""), 43 | """ 44 | import 'package:flutter/material.dart'; 45 | 46 | import 'package:impressive_dependency/impressive_dependency.dart'; 47 | 48 | void main() { 49 | runApp(const MyApp()); 50 | } 51 | 52 | class MyApp extends StatefulWidget { 53 | const MyApp({Key? key}) : super(key: key); 54 | 55 | @override 56 | State createState() => _MyAppState(); 57 | } 58 | 59 | class _MyAppState extends State { 60 | String _greeting = "There shall be no greeting for now!"; 61 | 62 | @override 63 | void initState() { 64 | super.initState(); 65 | greeting( 66 | state: this, 67 | onComplete: (_) => setState(() {}), 68 | onSuccess: (value) => _greeting = value, 69 | onFailure: (message) => _greeting = "He did not want to say Hi..." 70 | ); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return MaterialApp( 76 | home: Scaffold( 77 | appBar: AppBar( 78 | title: const Text('Plugin example app'), 79 | ), 80 | body: Center( 81 | child: Text(_greeting), 82 | ), 83 | ), 84 | ); 85 | } 86 | }""" 87 | .replaceAll(" ", "")); 88 | 89 | example.deleteSync(recursive: true); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /test/src/systemtest/e2e_test_with_config.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 - 2023 Buijs Software 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | @Tags(["ST"]) 22 | 23 | import "dart:io"; 24 | 25 | import "package:klutter/src/common/common.dart"; 26 | import "package:test/test.dart"; 27 | 28 | import "e2e_test.dart"; 29 | 30 | const organisation = "dev.buijs.integrationtest.example"; 31 | 32 | const pluginName = "ridiculous_awesome"; 33 | 34 | const appName = "my_flutter_app"; 35 | 36 | void main() { 37 | final pathToRoot = Directory( 38 | "${Directory.systemTemp.absolute.path}/createklutterpluginit2".normalize) 39 | ..maybeDelete 40 | ..createSync(); 41 | 42 | final producerPlugin = 43 | Directory("${pathToRoot.absolute.path}/$pluginName".normalize); 44 | 45 | test("end-to-end test", () async { 46 | /// Create Flutter plugin project. 47 | await createKlutterPlugin( 48 | organisation: organisation, 49 | pluginName: pluginName, 50 | root: pathToRoot.absolute.path, 51 | ); 52 | 53 | expect(producerPlugin.existsSync(), true, 54 | reason: 55 | "Plugin should be created in: '${producerPlugin.absolute.path}'"); 56 | 57 | /// Add Klutter as dev_dependency. 58 | createConfigYaml( 59 | root: producerPlugin.absolutePath, 60 | ); 61 | 62 | /// Root build.gradle file should be created. 63 | final rootBuildGradle = 64 | File("${producerPlugin.absolutePath}/build.gradle.kts".normalize); 65 | expect(rootBuildGradle.existsSync(), true, 66 | reason: "root/build.gradle.kts should exist"); 67 | expect(rootBuildGradle.readAsStringSync().contains("bom:9999.1.1"), true, 68 | reason: 69 | "root/build.gradle.kts should contains BOM version from config."); 70 | 71 | /// Root settings.gradle file should be created. 72 | expect( 73 | File("${producerPlugin.absolutePath}/settings.gradle.kts".normalize) 74 | .existsSync(), 75 | true, 76 | reason: "root/settings.gradle.kts should exist"); 77 | 78 | /// Android/Klutter build.gradle file should be created. 79 | final androidKlutterBuildGradle = File( 80 | "${producerPlugin.absolutePath}/android/klutter/build.gradle.kts" 81 | .normalize); 82 | expect(androidKlutterBuildGradle.existsSync(), true, 83 | reason: "android/klutter/build.gradle.kts should exist"); 84 | 85 | /// Android/Klutter build.gradle file should be created. 86 | final androidBuildGradle = 87 | File("${producerPlugin.absolutePath}/android/build.gradle".normalize); 88 | expect(androidBuildGradle.existsSync(), true, 89 | reason: "android/build.gradle.kts should exist"); 90 | expect(androidBuildGradle.readAsStringSync().contains("bom:9999.1.1"), true, 91 | reason: "android/build.gradle should contain BOM version from config."); 92 | }); 93 | 94 | tearDownAll(() => pathToRoot.deleteSync(recursive: true)); 95 | } 96 | 97 | /// Create Flutter plugin project. 98 | void createConfigYaml({ 99 | required String root, 100 | }) { 101 | Directory(root).resolveFile("kradle.yaml") 102 | ..createSync() 103 | ..writeAsStringSync(""" 104 | bom-version:9999.1.1 105 | """); 106 | } 107 | --------------------------------------------------------------------------------