├── .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://pub.dev/publishers/buijs.dev/packages)
2 | [](https://github.com/buijs-dev/klutter-dart/blob/main/LICENSE)
3 | [](https://pub.dev/packages/klutter)
4 | [](https://codecov.io/gh/buijs-dev/klutter-dart)
5 | [](https://codescene.io/projects/27237)
6 |
7 |
8 |
9 |
10 |
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 |
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 |
--------------------------------------------------------------------------------