From cac90ebcc0b25d5d9ac892892455877af502c557 Mon Sep 17 00:00:00 2001 From: Gillian <72627535+buijs-dev@users.noreply.github.com> Date: Sun, 12 May 2024 20:05:45 +0200 Subject: [PATCH 1/2] Add ios option to init task. (#16) Add ios option to init to set the ios version for producer and consumer. --- README.md | 6 +- lib/src/cli/option.dart | 22 +++++ lib/src/cli/task.dart | 5 ++ lib/src/cli/task_project_create.dart | 34 ------- lib/src/cli/task_project_init.dart | 90 ++++++++++++++----- lib/src/consumer/ios.dart | 16 ++-- lib/src/producer/ios.dart | 14 +-- test/src/cli/option_test.dart | 18 ++++ test/src/cli/task_project_init_test.dart | 2 +- test/src/cli/task_service_test.dart | 3 +- test/src/producer/ios_test.dart | 8 +- test/src/systemtest/e2e_test.dart | 6 +- test/src/systemtest/e2e_test_with_config.dart | 2 +- 13 files changed, 146 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 8213609..4f5dbd5 100644 --- a/README.md +++ b/README.md @@ -201,10 +201,10 @@ Make sure you have followed all the following steps: - [klutter](https://pub.dev/packages/klutter) is added to the dependencies in your pubspec.yaml (both the plugin and plugin/example for testing). - do flutter pub get in both root and root/example folder. -- do flutter pub run klutter:producer init in the root folder. +- do flutter pub run klutter:kradle init in the root folder. - do ./gradlew clean build -p "platform" in the root folder. -- do flutter pub run klutter:consumer init in the root/example folder. -- do flutter pub run klutter:consumer add lib= in the root/example folder. +- do flutter pub run klutter:kradle init in the root/example folder. +- do flutter pub run klutter:kradle add lib= in the root/example folder. ### For Android emulator: There should be a .klutter-plugins file in the root/example folder containing an entry for your plugin. diff --git a/lib/src/cli/option.dart b/lib/src/cli/option.dart index 6be2e8b..e97af08 100644 --- a/lib/src/cli/option.dart +++ b/lib/src/cli/option.dart @@ -207,6 +207,28 @@ class KlutterGradleVersionOption extends UserInputOrDefault { (throw InputException("not a valid bom version: $value")); } +/// Option to specify ios version which defaults to [iosVersion]. +class IosVersionOption extends UserInputOrDefault { + /// Construct a new [IosVersionOption] instance with default value [iosVersion]. + const IosVersionOption() : super(iosVersion); + + @override + String get description => "ios version"; + + @override + double convertOrThrow(String value) { + final version = double.tryParse(value) ?? + (throw InputException("not a valid ios version: $value")); + + if (version < iosVersion) { + throw InputException( + "ios version is too old (min version is $iosVersion): $value"); + } + + return version; + } +} + /// Option to specify plugin_name which defaults to my_plugin. class PluginNameOption extends UserInputOrDefault { /// Construct a new [PluginNameOption] instance with default value my_plugin. diff --git a/lib/src/cli/task.dart b/lib/src/cli/task.dart index 2f79968..2bab279 100644 --- a/lib/src/cli/task.dart +++ b/lib/src/cli/task.dart @@ -239,6 +239,9 @@ enum TaskOption { /// The squint pub version in format major.minor.patch. squint, + + /// The iOS version to use. + ios, } /// Convert a String value to a [TaskName]. @@ -261,6 +264,8 @@ extension TaskOptionParser on String? { return TaskOption.flutter; case "GROUP": return TaskOption.group; + case "IOS": + return TaskOption.ios; case "KLUTTER": return TaskOption.klutter; case "KLUTTERUI": diff --git a/lib/src/cli/task_project_create.dart b/lib/src/cli/task_project_create.dart index 9913b13..dec6d31 100644 --- a/lib/src/cli/task_project_create.dart +++ b/lib/src/cli/task_project_create.dart @@ -21,7 +21,6 @@ import "dart:io"; import "../common/common.dart"; -import "../consumer/consumer.dart"; import "cli.dart"; import "context.dart"; @@ -140,27 +139,6 @@ class CreateProject extends Task { exampleFolder ..deleteTestFolder ..deleteIntegrationTestFolder; - - if (platform.isMacos) { - exampleFolder - ..deleteIosPodfileLock - ..deleteIosPods - ..deleteRunnerXCWorkspace; - - final iosWorkingDirectory = root - .resolveDirectory("example") - .resolveDirectory("ios") - ..verifyDirectoryExists; - - setIosVersionInPodFile(iosWorkingDirectory); - for (final step in ["install", "update"]) { - _executor - ..executable = "pod" - ..workingDirectory = iosWorkingDirectory - ..arguments = [step] - ..run(); - } - } } } @@ -325,18 +303,6 @@ extension on Directory { }); } - void get deleteIosPodfileLock { - resolveDirectory("ios").resolveFile("Podfile.lock").maybeDelete; - } - - void get deleteIosPods { - resolveDirectory("ios").resolveDirectory("Pods").maybeDelete; - } - - void get deleteRunnerXCWorkspace { - resolveDirectory("ios").resolveDirectory("Runner.xcworkspace").maybeDelete; - } - void overwriteReadmeFile(String pluginName) { final readme = resolveFile("README.md"); diff --git a/lib/src/cli/task_project_init.dart b/lib/src/cli/task_project_init.dart index b62198c..3a19acf 100644 --- a/lib/src/cli/task_project_init.dart +++ b/lib/src/cli/task_project_init.dart @@ -23,7 +23,6 @@ import "dart:io"; import "../common/common.dart"; -import "../consumer/android.dart"; import "../consumer/consumer.dart"; import "../producer/android.dart"; import "../producer/gradle.dart"; @@ -47,12 +46,17 @@ const _resourceTarUrl = /// {@category tasks} class ProjectInit extends Task { /// Create new Task based of the root folder. - ProjectInit() + ProjectInit({Executor? executor}) : super(TaskName.init, { TaskOption.bom: const KlutterGradleVersionOption(), TaskOption.flutter: FlutterVersionOption(), TaskOption.root: RootDirectoryInput(), - }); + TaskOption.ios: const IosVersionOption() + }) { + _executor = executor ?? Executor(); + } + + late final Executor _executor; @override Future toBeExecuted( @@ -70,32 +74,74 @@ class ProjectInit extends Task { isProducerProject = false; } + final ios = options[TaskOption.ios]; if (isProducerProject) { print("initializing klutter project as producer"); final bom = options[TaskOption.bom]; final flutter = options[TaskOption.flutter] as VerifiedFlutterVersion; - await _producerInit(pathToRoot, bom, flutter); - _consumerInit("$pathToRoot/example".normalize); + await _producerInit(pathToRoot, bom, flutter, ios); + _consumerInit("$pathToRoot/example".normalize, ios); } else { print("initializing klutter project as consumer"); - _consumerInit(pathToRoot); + _consumerInit(pathToRoot, ios); } } + + void _consumerInit(String pathToRoot, double iosVersion) { + final pathToAndroid = "$pathToRoot/android".normalize; + final sdk = findFlutterSDK(pathToAndroid); + final app = "$pathToAndroid/app".normalize; + writePluginLoaderGradleFile(sdk); + createRegistry(pathToRoot); + applyPluginLoader(pathToAndroid); + setAndroidSdkConstraints(app); + setKotlinVersionInBuildGradle(pathToAndroid); + if (platform.isMacos) { + _setupIOS(Directory(pathToRoot), iosVersion); + } + } + + void _setupIOS(Directory exampleDirectory, double iosVersion) { + exampleDirectory + ..deleteIosPodfileLock + ..deleteIosPods + ..deleteRunnerXCWorkspace; + + final iosWorkingDirectory = exampleDirectory.resolveDirectory("ios") + ..verifyDirectoryExists; + + void doPodStep(String step) { + _executor + ..executable = "pod" + ..workingDirectory = iosWorkingDirectory + ..arguments = [step] + ..run(); + } + + doPodStep("install"); + setIosVersionInPodFile(iosWorkingDirectory, iosVersion); + doPodStep("deintegrate"); + doPodStep("install"); + doPodStep("update"); + } } -void _consumerInit(String pathToRoot) { - final pathToAndroid = "$pathToRoot/android".normalize; - final sdk = findFlutterSDK(pathToAndroid); - final app = "$pathToAndroid/app".normalize; - writePluginLoaderGradleFile(sdk); - createRegistry(pathToRoot); - applyPluginLoader(pathToAndroid); - setAndroidSdkConstraints(app); - setKotlinVersionInBuildGradle(pathToAndroid); +extension on Directory { + void get deleteIosPodfileLock { + resolveDirectory("ios").resolveFile("Podfile.lock").maybeDelete; + } + + void get deleteIosPods { + resolveDirectory("ios").resolveDirectory("Pods").maybeDelete; + } + + void get deleteRunnerXCWorkspace { + resolveDirectory("ios").resolveDirectory("Runner.xcworkspace").maybeDelete; + } } -Future _producerInit( - String pathToRoot, String bom, VerifiedFlutterVersion flutter) async { +Future _producerInit(String pathToRoot, String bom, + VerifiedFlutterVersion flutter, double iosVersion) async { final resources = await _downloadResourcesZipOrThrow(pathToRoot); final producer = _Producer( resourcesDirectory: resources, @@ -107,7 +153,7 @@ Future _producerInit( producer ..setupRoot ..setupAndroid - ..setupIOS + ..setupIOS(iosVersion) ..setupPlatform ..setupExample; } @@ -242,13 +288,13 @@ extension on _Producer { setGradleWrapperVersion(pathToAndroid: pathToAndroid); } - void get setupIOS { + void setupIOS(double iosVersion) { final pathToIos = "$pathToRoot/ios"; createIosKlutterFolder(pathToIos); addFrameworkAndSetIosVersionInPodspec( - pathToIos: "$pathToRoot/ios", - pluginName: findPluginName(pathToRoot), - ); + pathToIos: "$pathToRoot/ios", + pluginName: findPluginName(pathToRoot), + iosVersion: iosVersion); } Future get addGradle async { diff --git a/lib/src/consumer/ios.dart b/lib/src/consumer/ios.dart index 53ff159..ce3f5e3 100644 --- a/lib/src/consumer/ios.dart +++ b/lib/src/consumer/ios.dart @@ -25,13 +25,15 @@ import "../common/common.dart"; /// Create the root/ios/Klutter directory and add a readme file. /// /// {@category consumer} -void setIosVersionInPodFile(Directory iosDirectory) => - iosDirectory.resolveFile("Podfile") - ..verifyFileExists - ..setIosVersion; +void setIosVersionInPodFile(Directory iosDirectory, double version) { + final podFile = iosDirectory.resolveFile("Podfile"); + if (podFile.existsSync()) { + podFile.setIosVersion(version); + } +} extension on File { - void get setIosVersion { + void setIosVersion(double version) { // INPUT final lines = readAsLinesSync(); @@ -46,7 +48,7 @@ extension on File { // Check if line sets ios platform version // and if so then update the version. if (trimmed.contains("platform:ios,")) { - newLines.add("platform :ios, '$iosVersion'"); + newLines.add("platform :ios, '$version'"); hasExplicitPlatformVersion = true; } else { newLines.add(line); @@ -57,7 +59,7 @@ extension on File { throw const KlutterException("Failed to set ios version in Podfile."); } - // Write the editted line to the podspec file. + // Write the edited line to the podspec file. writeAsStringSync(newLines.join("\n")); } } diff --git a/lib/src/producer/ios.dart b/lib/src/producer/ios.dart index a90e607..83c44fa 100644 --- a/lib/src/producer/ios.dart +++ b/lib/src/producer/ios.dart @@ -35,11 +35,13 @@ void createIosKlutterFolder(String pathToIos) => pathToIos.verifyExists /// The generated framework will be copied to the root/ios/Klutter folder. /// /// {@category producer} -void addFrameworkAndSetIosVersionInPodspec({ - required String pathToIos, - required String pluginName, -}) => - pathToIos.verifyExists.toPodspec(pluginName).addFrameworkAndSetIosVersion; +void addFrameworkAndSetIosVersionInPodspec( + {required String pathToIos, + required String pluginName, + required double iosVersion}) => + pathToIos.verifyExists + .toPodspec(pluginName) + .addFrameworkAndSetIosVersion(iosVersion); extension on String { void get createKlutterFolder { @@ -58,7 +60,7 @@ extension on String { } extension on File { - void get addFrameworkAndSetIosVersion { + void addFrameworkAndSetIosVersion(double iosVersion) { final regex = RegExp("Pod::Spec.new.+?do.+?.([^|]+?)."); /// Check the prefix used in the podspec or default to 's'. diff --git a/test/src/cli/option_test.dart b/test/src/cli/option_test.dart index 88e5920..1436fe1 100644 --- a/test/src/cli/option_test.dart +++ b/test/src/cli/option_test.dart @@ -40,6 +40,24 @@ void main() { "2025.1.1.beta"); }); + test("Verify IosVersionOption throws exception if input is invalid", () { + expect( + () => const IosVersionOption().convertOrThrow("doubleCheeseBurger"), + throwsA(predicate((e) => + e is InputException && + e.cause == "not a valid ios version: doubleCheeseBurger"))); + }); + + test("Verify IosVersionOption throws exception if ios version is too old", + () { + expect( + () => const IosVersionOption().convertOrThrow("10"), + throwsA(predicate((e) => + e is InputException && + e.cause == + "ios version is too old (min version is $iosVersion): 10"))); + }); + test("KlutterGradleVersionOption throws exception if input is invalid", () { expect( () => const KlutterGradleVersionOption().convertOrThrow("spidey.2099"), diff --git a/test/src/cli/task_project_init_test.dart b/test/src/cli/task_project_init_test.dart index 0560c4e..a6c6882 100644 --- a/test/src/cli/task_project_init_test.dart +++ b/test/src/cli/task_project_init_test.dart @@ -42,7 +42,7 @@ void main() { final task = ProjectInit(); final context = Context(pathToConsumer, {}); final result = await task.execute(context); - expect(result.isOk, true); + expect(result.isOk, true, reason: result.message); final file = pathToConsumer.resolveFile("/lib/main.dart"); var reason = "example/lib/main.dart file should exist"; expect(file.existsSync(), true, reason: reason); diff --git a/test/src/cli/task_service_test.dart b/test/src/cli/task_service_test.dart index d70ed29..45ba0d6 100644 --- a/test/src/cli/task_service_test.dart +++ b/test/src/cli/task_service_test.dart @@ -44,7 +44,7 @@ void main() { expect( tasks.getTask(TaskName.init).toString(), - "init\n bom (Optional) klutter gradle version. Defaults to '2024.1.1.beta'.\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", + "init\n bom (Optional) klutter gradle version. Defaults to '2024.1.1.beta'.\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", ); }); @@ -78,6 +78,7 @@ void main() { " bom (Optional) klutter gradle version. Defaults to '2024.1.1.beta'.\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" "\n" "get\n" " flutter (Optional) flutter sdk version in format major.minor.patch. Defaults to '3.10.6'.\n" diff --git a/test/src/producer/ios_test.dart b/test/src/producer/ios_test.dart index 0ad3601..380a1e5 100644 --- a/test/src/producer/ios_test.dart +++ b/test/src/producer/ios_test.dart @@ -32,7 +32,9 @@ void main() { expect( () => addFrameworkAndSetIosVersionInPodspec( - pluginName: "some_plugin", pathToIos: folder.absolutePath), + pluginName: "some_plugin", + pathToIos: folder.absolutePath, + iosVersion: 15), throwsA(predicate((e) => e is KlutterException && e.cause.startsWith("Missing podspec file")))); @@ -46,7 +48,9 @@ void main() { expect( () => addFrameworkAndSetIosVersionInPodspec( - pluginName: "some_plugin", pathToIos: folder.absolutePath), + pluginName: "some_plugin", + pathToIos: folder.absolutePath, + iosVersion: 15), throwsA(predicate((e) => e is KlutterException && e.cause.startsWith( diff --git a/test/src/systemtest/e2e_test.dart b/test/src/systemtest/e2e_test.dart index a5220d0..a8c5411 100644 --- a/test/src/systemtest/e2e_test.dart +++ b/test/src/systemtest/e2e_test.dart @@ -83,7 +83,7 @@ void main() { reason: "the cached sdk should not be empty"); /// Create Flutter plugin project. - final createResult = await createFlutterPlugin( + final createResult = await createKlutterPlugin( organisation: organisation, pluginName: pluginName, root: Directory(pathToRoot.absolutePath) @@ -244,8 +244,8 @@ void main() { tearDownAll(() => pathToRoot.deleteSync(recursive: true)); } -/// Create Flutter plugin project. -Future createFlutterPlugin({ +/// Create klutter plugin project. +Future createKlutterPlugin({ required String organisation, required String pluginName, required String root, diff --git a/test/src/systemtest/e2e_test_with_config.dart b/test/src/systemtest/e2e_test_with_config.dart index 77427a5..75b9be9 100644 --- a/test/src/systemtest/e2e_test_with_config.dart +++ b/test/src/systemtest/e2e_test_with_config.dart @@ -44,7 +44,7 @@ void main() { test("end-to-end test", () async { /// Create Flutter plugin project. - await createFlutterPlugin( + await createKlutterPlugin( organisation: organisation, pluginName: pluginName, root: pathToRoot.absolute.path, From 5e7de92b91c52a2266b663f176e703f6b838f5b3 Mon Sep 17 00:00:00 2001 From: Gillian <72627535+buijs-dev@users.noreply.github.com> Date: Mon, 13 May 2024 13:45:37 +0200 Subject: [PATCH 2/2] Develop (#17) Propagate flutter version correctly to GetFlutterTask when running ProjectCreate. --- lib/src/cli/task.dart | 2 +- lib/src/cli/task_project_create.dart | 12 +++++++++++- test/src/cli/task_project_create_test.dart | 19 ++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/src/cli/task.dart b/lib/src/cli/task.dart index 2bab279..4108f9b 100644 --- a/lib/src/cli/task.dart +++ b/lib/src/cli/task.dart @@ -50,7 +50,7 @@ abstract class Task { /// Execute the task and return instance of [T] or throw /// [KlutterException] if unsuccessful. Future executeOrThrow(Context context) async { - return await toBeExecuted(context, _getOptions(context)); + return toBeExecuted(context, _getOptions(context)); } /// The validated options. diff --git a/lib/src/cli/task_project_create.dart b/lib/src/cli/task_project_create.dart index dec6d31..949519f 100644 --- a/lib/src/cli/task_project_create.dart +++ b/lib/src/cli/task_project_create.dart @@ -69,7 +69,11 @@ class CreateProject extends Task { final dist = toFlutterDistributionOrThrow( version: flutterVersion, pathToRoot: pathToRoot); - final result = await _getFlutterSDK.executeOrThrow(context); + final result = + await _getFlutterSDK.executeOrThrow(context.copyWith(taskOptions: { + TaskOption.flutter: dist.folderNameString.toString(), + })); + final flutter = result.resolveFile("flutter/bin/flutter".normalize).absolutePath; final root = await createFlutterProjectOrThrow( @@ -136,6 +140,12 @@ class CreateProject extends Task { TaskOption.lib: name, })); + _executor + ..workingDirectory = root + ..arguments = ["klutterGetKradle", "-p", "platform"] + ..executable = root.resolveFile("gradlew").absolutePath + ..run(); + exampleFolder ..deleteTestFolder ..deleteIntegrationTestFolder; diff --git a/test/src/cli/task_project_create_test.dart b/test/src/cli/task_project_create_test.dart index 45d87bd..b2c03c6 100644 --- a/test/src/cli/task_project_create_test.dart +++ b/test/src/cli/task_project_create_test.dart @@ -61,11 +61,28 @@ void main() { expect(result.isOk, false); expect(result.message, "BOOM!"); }); + + test("Verify flutter option is used correctly", () async { + const version = "3.0.5.macos.arm64"; + final getFlutterTask = FlutterFixedVersion(); + final task = CreateProject(getFlutterSDK: getFlutterTask); + final result = await task + .execute(Context(Directory.systemTemp, {TaskOption.flutter: version})); + expect(result.isOk, false); + expect(result.message, version); + }); } class NoFlutterSDK extends GetFlutterSDK { @override Future executeOrThrow(Context context) async { - throw KlutterException("BOOM!"); + throw const KlutterException("BOOM!"); + } +} + +class FlutterFixedVersion extends GetFlutterSDK { + @override + Future executeOrThrow(Context context) async { + throw KlutterException(context.taskOptions[TaskOption.flutter] ?? "--"); } }