├── .github └── workflows │ ├── compilation-check.yml │ └── publish.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml └── copyright │ ├── IceRock.xml │ └── profiles_settings.xml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kswift-gradle-plugin ├── build.gradle.kts ├── gradle.properties ├── settings.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── dev │ │ └── icerock │ │ └── moko │ │ └── kswift │ │ └── plugin │ │ ├── KLibProcessor.kt │ │ ├── KSwiftExtension.kt │ │ ├── KSwiftPlugin.kt │ │ ├── KSwiftPodspecTask.kt │ │ ├── KSwiftRuntimeAnnotations.kt │ │ ├── KmAnnotationExt.kt │ │ ├── KmClassExt.kt │ │ ├── KmConstructorExt.kt │ │ ├── KmTypeExt.kt │ │ ├── KotlinMetadataLibraryProvider.kt │ │ ├── PostProcessLinkTask.kt │ │ ├── context │ │ ├── ChildContext.kt │ │ ├── ClassContext.kt │ │ ├── FeatureContext.kt │ │ ├── FragmentContext.kt │ │ ├── LibraryContext.kt │ │ ├── PackageContext.kt │ │ └── PackageFunctionContext.kt │ │ └── feature │ │ ├── BaseConfig.kt │ │ ├── DataClassCopyFeature.kt │ │ ├── Filter.kt │ │ ├── PlatformExtensionFunctionsFeature.kt │ │ ├── ProcessorFeature.kt │ │ └── SealedToSwiftEnumFeature.kt │ └── test │ ├── kotlin │ └── dev │ │ └── icerock │ │ └── moko │ │ └── kswift │ │ └── plugin │ │ ├── KmClassExtKtTest.kt │ │ ├── PlatformExtensionsTest.kt │ │ └── klibUtils.kt │ └── resources │ ├── mpp-library.klib │ └── mvvm-livedata.klib ├── kswift-runtime ├── build.gradle.kts └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── dev │ └── icerock │ └── moko │ └── kswift │ └── Annotations.kt ├── sample ├── android-app │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── icerockdev │ │ │ └── app │ │ │ └── MainActivity.kt │ │ └── res │ │ └── layout │ │ └── activity_main.xml ├── ios-app │ ├── Podfile │ ├── Podfile.lock │ ├── Tests │ │ ├── AnyObjectSealedClassToEnum.swift │ │ ├── DataClassCopyTests.swift │ │ ├── ExternalGenericSealedClassesToSwiftEnumTests.swift │ │ ├── ExternalNonGenericSealedClassesToSwiftEnumTests.swift │ │ ├── GenericSealedClassesToSwiftEnumTests.swift │ │ ├── Info.plist │ │ ├── NonGenericSealedClassesToSwiftEnumTests.swift │ │ ├── PlatformExtensionsTests.swift │ │ ├── SealedClassToSwiftEnumTests.swift │ │ └── SealedInterfaceToSwiftEnumTests.swift │ ├── ios-app.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── ios-app.xcscheme │ │ │ └── pods-test.xcscheme │ ├── ios-app.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── pods-test │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── SceneDelegate.swift │ │ └── ViewController.swift │ └── src │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── Resources │ │ └── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ └── TestViewController.swift ├── mpp-library-pods │ ├── build.gradle │ ├── mpp_library_pods.podspec │ ├── mpp_library_podsSwift.podspec │ └── src │ │ ├── androidMain │ │ └── AndroidManifest.xml │ │ ├── commonMain │ │ └── kotlin │ │ │ └── com │ │ │ └── icerockdev │ │ │ └── library │ │ │ ├── TestStateSource.kt │ │ │ └── UIState.kt │ │ └── iosMain │ │ └── kotlin │ │ └── com │ │ └── icerockdev │ │ └── library │ │ └── PlatformExtensions.kt └── mpp-library │ ├── MultiPlatformLibrary.podspec │ ├── MultiPlatformLibrarySwift.podspec │ ├── build.gradle.kts │ └── src │ ├── androidMain │ └── AndroidManifest.xml │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── icerockdev │ │ └── library │ │ ├── DataClass.kt │ │ ├── DataClassWithCollections.kt │ │ ├── ExcludedSealed.kt │ │ ├── Extensions.kt │ │ ├── ExternalGenericSealedClass.kt │ │ ├── ExternalNonGenericSealedClass.kt │ │ ├── GenericSealedClass.kt │ │ ├── NonGenericSealedClass.kt │ │ ├── Status.kt │ │ └── UIState.kt │ └── iosMain │ └── kotlin │ └── com │ └── icerockdev │ └── library │ ├── PlatformExtensions.kt │ └── UILabelExt.kt └── settings.gradle.kts /.github/workflows/compilation-check.yml: -------------------------------------------------------------------------------- 1 | name: KMP library compilation check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | os: [ macos-latest, windows-latest, ubuntu-latest ] 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Set up JDK 11 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 11 22 | - name: Plugin check 23 | run: ./gradlew -p kswift-gradle-plugin build publishToMavenLocal 24 | if: matrix.os == 'ubuntu-latest' 25 | - name: Check build 26 | run: ./gradlew build publishToMavenLocal syncMultiPlatformLibraryDebugFrameworkIosX64 podspec kSwiftMultiPlatformLibraryPodspec kSwiftmpp_library_podsPodspec 27 | if: matrix.os == 'macOS-latest' 28 | - name: Check build 29 | run: ./gradlew build publishToMavenLocal 30 | if: matrix.os != 'macOS-latest' 31 | - name: Install pods 32 | run: cd sample/ios-app && pod install 33 | if: matrix.os == 'macOS-latest' 34 | - name: Check iOS 35 | run: cd sample/ios-app && set -o pipefail && xcodebuild -scheme ios-app -workspace ios-app.xcworkspace test -destination "platform=iOS Simulator,name=iPhone 12 mini" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcpretty 36 | if: matrix.os == 'macOS-latest' -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version' 8 | default: '0.1.0' 9 | required: true 10 | 11 | jobs: 12 | publish: 13 | name: Publish library at mavenCentral 14 | runs-on: ${{ matrix.os }} 15 | env: 16 | OSSRH_USER: ${{ secrets.OSSRH_USER }} 17 | OSSRH_KEY: ${{ secrets.OSSRH_KEY }} 18 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEYID }} 19 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 20 | SIGNING_KEY: ${{ secrets.GPG_KEY_CONTENTS }} 21 | strategy: 22 | matrix: 23 | os: [macos-latest, windows-latest, ubuntu-latest] 24 | 25 | steps: 26 | - uses: actions/checkout@v1 27 | - name: Set up JDK 11 28 | uses: actions/setup-java@v1 29 | with: 30 | java-version: 11 31 | - name: Plugin publish 32 | run: ./gradlew -p kswift-gradle-plugin publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PLUGIN_PORTAL_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PLUGIN_PORTAL_SECRET }} 33 | if: matrix.os == 'ubuntu-latest' 34 | - name: Runtime publish 35 | run: ./gradlew publish -DIS_MAIN_HOST=${{ matrix.os == 'ubuntu-latest' }} 36 | 37 | release: 38 | name: Create release 39 | needs: publish 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Create Release 43 | id: create_release 44 | uses: actions/create-release@v1 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | with: 48 | commitish: ${{ github.ref }} 49 | tag_name: release/${{ github.event.inputs.version }} 50 | release_name: ${{ github.event.inputs.version }} 51 | body: "Will be filled later" 52 | draft: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .project 4 | .classpath 5 | .vscode 6 | .idea 7 | build 8 | *.iml 9 | Pods 10 | xcuserdata 11 | local.properties 12 | kotlin-js-store/ -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/copyright/IceRock.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Do’s and Don’ts 2 | 3 | * **Search tickets before you file a new one.** Add to tickets if you have new information about the issue. 4 | * **Keep tickets short but sweet.** Make sure you include all the context needed to solve the issue. Don't overdo it. Great tickets allow us to focus on solving problems instead of discussing them. 5 | * **Take care of your ticket.** When you spend time to report a ticket with care we'll enjoy fixing it for you. 6 | * **Use [GitHub-flavored Markdown](https://help.github.com/articles/markdown-basics/).** Especially put code blocks and console outputs in backticks (```` ``` ````). That increases the readability. Bonus points for applying the appropriate syntax highlighting. 7 | 8 | ## Bug Reports 9 | 10 | In short, since you are most likely a developer, provide a ticket that you _yourself_ would _like_ to receive. 11 | 12 | First check if you are using the latest library version and Kotlin version before filing a ticket. 13 | 14 | Please include steps to reproduce and _all_ other relevant information, including any other relevant dependency and version information. 15 | 16 | ## Feature Requests 17 | 18 | Please try to be precise about the proposed outcome of the feature and how it 19 | would related to existing features. 20 | 21 | 22 | ## Pull Requests 23 | 24 | We **love** pull requests! 25 | 26 | All contributions _will_ be licensed under the Apache 2 license. 27 | 28 | Code/comments should adhere to the following rules: 29 | 30 | * Names should be descriptive and concise. 31 | * Use four spaces and no tabs. 32 | * Remember that source code usually gets written once and read often: ensure 33 | the reader doesn't have to make guesses. Make sure that the purpose and inner 34 | logic are either obvious to a reasonably skilled professional, or add a 35 | comment that explains it. 36 | * Please add a detailed description. 37 | 38 | If you consistently contribute improvements and/or bug fixes, we're happy to make you a maintainer. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![moko-kswift](https://user-images.githubusercontent.com/5010169/128708262-81403e12-4ddb-4ff6-8d9d-feda2fd95bf3.png) 2 | [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/dev.icerock.moko/kswift-runtime) ](https://repo1.maven.org/maven2/dev/icerock/moko/kswift-runtime) ![kotlin-version](https://kotlin-version.aws.icerock.dev/kotlin-version?group=dev.icerock.moko&name=kswift-runtime) 3 | 4 | # MOKO KSwift 5 | 6 | KSwift it's gradle plugin for generation Swift-friendly API for Kotlin/Native framework. 7 | 8 | ## Kotlin sealed interface/class to Swift enum 9 | 10 | ![sealed classes compare](https://user-images.githubusercontent.com/5010169/128596479-688bce59-b224-402f-8f5b-8b32d33aa0b5.png) 11 | 12 | ## Kotlin extensions for K/N platform classes 13 | 14 | ![extensions compare](https://user-images.githubusercontent.com/5010169/128596630-28af4922-fcee-4eae-979b-0bad3722f0d2.png) 15 | 16 | ## Your own case 17 | 18 | KSwift give you API for adding your own generator based on KLib metadata information. 19 | 20 | # Posts 21 | 22 | - [How to implement Swift-friendly API with Kotlin Multiplatform Mobile](https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d) 23 | 24 | # Table of Contents 25 | 26 | - [Features](#features) 27 | - [Requirements](#requirements) 28 | - [Installation](#installation) 29 | - [Usage](#usage) 30 | - [FAQ](#faq) 31 | - [Samples](#samples) 32 | - [Set Up Locally](#set-up-locally) 33 | - [Contributing](#contributing) 34 | - [License](#license) 35 | 36 | # Features 37 | 38 | - **API for extend logic for own cases** - just implement your own `ProcessorFeature` 39 | - **Reading of all exported klibs** - you can generate swift additions to the api of external 40 | libraries 41 | - **Kotlin sealed class/interface to Swift enum** 42 | - **Kotlin extensions for platform classes to correct extensions** instead of additional class with 43 | static methods 44 | - **Flexible filtration** - select what you want to generate and what not 45 | 46 | # Requirements 47 | 48 | - Gradle version 6.0+ 49 | - Kotlin 1.6.10 50 | 51 | # Installation 52 | 53 | ## Plugin 54 | 55 | ### Using legacy plugin application 56 | 57 | root `build.gradle` 58 | 59 | ```groovy 60 | buildscript { 61 | repositories { 62 | mavenCentral() 63 | google() 64 | gradlePluginPortal() 65 | } 66 | dependencies { 67 | classpath("dev.icerock.moko:kswift-gradle-plugin:0.7.0") 68 | } 69 | } 70 | ``` 71 | 72 | project where framework compiles `build.gradle` 73 | 74 | ```groovy 75 | plugins { 76 | id("dev.icerock.moko.kswift") 77 | } 78 | ``` 79 | 80 | ### Using the plugins DSL 81 | 82 | `settings.gradle` 83 | 84 | ```groovy 85 | pluginManagement { 86 | repositories { 87 | google() 88 | gradlePluginPortal() 89 | mavenCentral() 90 | } 91 | } 92 | ``` 93 | 94 | project where framework compiles `build.gradle` 95 | 96 | ```groovy 97 | plugins { 98 | id("dev.icerock.moko.kswift") version "0.7.0" 99 | } 100 | ``` 101 | 102 | ## Runtime library 103 | 104 | root `build.gradle` 105 | 106 | ```groovy 107 | allprojects { 108 | repositories { 109 | mavenCentral() 110 | } 111 | } 112 | ``` 113 | 114 | project `build.gradle` 115 | 116 | ```groovy 117 | dependencies { 118 | commonMainApi("dev.icerock.moko:kswift-runtime:0.7.0") // if you want use annotations 119 | } 120 | ``` 121 | 122 | # Usage 123 | 124 | ## Xcode configuration 125 | 126 | The Swift code generated from this plugin is not automatically included in the shared framework you might have. 127 | 128 | You have 2 options to use it in your iOS project: 129 | - Xcode direct file integration 130 | - CocoaPods integration 131 | 132 | ### Xcode direct file integration 133 | 134 | You can directly import the generated file in your Xcode project like it's a file you have written on your own. 135 | 136 | To do so: 137 | - open the Xcode project 138 | - right click on "iosApp" 139 | - choose "Add files to iOSApp" 140 | - add the file from the generated folder (you might need to read the FAQ to know where the generated folder is) 141 | - you are now good to go! 142 | 143 | ### CocoaPods integration 144 | 145 | After you have added the moko-kswift plugin to your shared module and synced your project, a new Gradle task should appear with name `kSwiftXXXXXPodspec` where `XXXXX` is the name of your shared module (so your task might be named `kSwiftsharedPodspec`). 146 | 147 | - Run the task doing `./gradlew kSwiftsharedPodspec` from the root of your project. 148 | This will generate a new podspec file, `XXXXXSwift.podspec`, where `XXXXX` is still the name of your shared module (so e.g. `sharedSwift.podspec`) 149 | 150 | - Now edit the `Podfile` inside the iOS project adding this line 151 | `pod 'sharedSwift', :path => '../shared'` 152 | just after the one already there for the already available shared module 153 | `pod 'shared', :path => '../shared'` 154 | 155 | - Now run `pod install` from the `iosApp` folder so the new framework is linked to your project. 156 | 157 | - Whenever you need a Swift file generated from moko-kswift just import the generated module (e.g. `import sharedSwift`) and you are good to go! 158 | 159 | ## Sealed classes/interfaces to Swift enum 160 | 161 | Enable feature in project `build.gradle`: 162 | 163 | kotlin: 164 | ```kotlin 165 | kswift { 166 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature) 167 | } 168 | ``` 169 | 170 | groovy: 171 | ```groovy 172 | kswift { 173 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature.factory) 174 | } 175 | ``` 176 | 177 | That's all - after this setup all sealed classes and sealed interfaces will be parsed by plugin and 178 | plugin will generate Swift enums for this classes. 179 | 180 | For example if you have in your kotlin code: 181 | 182 | ```kotlin 183 | sealed interface UIState { 184 | object Loading : UIState 185 | object Empty : UIState 186 | data class Data(val value: T) : UIState 187 | data class Error(val throwable: Throwable) : UIState 188 | } 189 | ``` 190 | 191 | Then plugin will generate source code: 192 | 193 | ```swift 194 | /** 195 | * selector: ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState */ 196 | public enum UIStateKs { 197 | 198 | case loading 199 | case empty 200 | case data(UIStateData) 201 | case error(UIStateError) 202 | 203 | public init(_ obj: UIState) { 204 | if obj is shared.UIStateLoading { 205 | self = .loading 206 | } else if obj is shared.UIStateEmpty { 207 | self = .empty 208 | } else if let obj = obj as? shared.UIStateData { 209 | self = .data(obj) 210 | } else if let obj = obj as? shared.UIStateError { 211 | self = .error(obj) 212 | } else { 213 | fatalError("UIStateKs not syncronized with UIState class") 214 | } 215 | } 216 | 217 | } 218 | ``` 219 | 220 | For each generated entry in comment generated `selector` - value of this selector can be used for 221 | filter. By default all entries generated. But if generated code invalid (please report issue in this 222 | case) you can disable generation of this particular entry: 223 | 224 | kotlin: 225 | ```kotlin 226 | kswift { 227 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature) { 228 | filter = excludeFilter("ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState") 229 | } 230 | } 231 | ``` 232 | 233 | groovy: 234 | ```groovy 235 | kswift { 236 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature.factory) { 237 | it.filter = it.excludeFilter("ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState") 238 | } 239 | } 240 | ``` 241 | 242 | As alternative you can use `includeFilter` to explicit setup each required for generation entries: 243 | 244 | kotlin: 245 | ```kotlin 246 | kswift { 247 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature) { 248 | filter = includeFilter("ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState") 249 | } 250 | } 251 | ``` 252 | 253 | groovy: 254 | ```groovy 255 | kswift { 256 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature.factory) { 257 | it.filter = it.includeFilter("ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState") 258 | } 259 | } 260 | ``` 261 | 262 | ## Extensions from platform classes 263 | 264 | Enable feature in project `build.gradle`: 265 | 266 | kotlin: 267 | ```kotlin 268 | kswift { 269 | install(dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature) 270 | } 271 | ``` 272 | 273 | groovy: 274 | ```groovy 275 | kswift { 276 | install(dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature.factory) 277 | } 278 | ``` 279 | 280 | That's all - after this setup all extension functions for classes from `platform.*` package will be 281 | correct swift code. 282 | 283 | For example if you have in your kotlin code: 284 | 285 | ```kotlin 286 | class CFlow(private val stateFlow: StateFlow) : StateFlow by stateFlow 287 | 288 | fun UILabel.bindText(coroutineScope: CoroutineScope, flow: CFlow) { 289 | val label = this 290 | coroutineScope.launch { 291 | label.text = flow.value 292 | flow.collect { label.text = it } 293 | } 294 | } 295 | ``` 296 | 297 | Then plugin will generate source code: 298 | 299 | ```swift 300 | public extension UIKit.UILabel { 301 | /** 302 | * selector: PackageFunctionContext/moko-kswift.sample:mpp-library/com.icerockdev.library/Class(name=platform/UIKit/UILabel)/bindText/coroutineScope:Class(name=kotlinx/coroutines/CoroutineScope),flow:Class(name=com/icerockdev/library/CFlow) */ 303 | public func bindText(coroutineScope: CoroutineScope, flow: CFlow) { 304 | return UILabelExtKt.bindText(self, coroutineScope: coroutineScope, flow: flow) 305 | } 306 | } 307 | ``` 308 | 309 | Selector from comment can be used for filters as in first example. 310 | 311 | ## Generation of Swift copy method for data classes 312 | 313 | Enable feature in project `build.gradle`: 314 | 315 | kotlin: 316 | ```kotlin 317 | kswift { 318 | install(dev.icerock.moko.kswift.plugin.feature.DataClassCopyFeature) 319 | } 320 | ``` 321 | 322 | groovy: 323 | ```groovy 324 | kswift { 325 | install(dev.icerock.moko.kswift.plugin.feature.DataClassCopyFeature.factory) 326 | } 327 | ``` 328 | 329 | With this feature for data class like this: 330 | ```kotlin 331 | data class DataClass( 332 | val stringValue: String, 333 | val optionalStringValue: String?, 334 | val intValue: Int, 335 | val optionalIntValue: Int?, 336 | val booleanValue: Boolean, 337 | val optionalBooleanValue: Boolean? 338 | ) 339 | ``` 340 | 341 | Will be generated swift code that can be used like this: 342 | ```swift 343 | let newObj = dataClass.copy( 344 | stringValue: {"aNewValue"}, 345 | intValue: {1}, 346 | booleanValue: {true} 347 | ) 348 | ``` 349 | 350 | ## Implementation of own generator 351 | 352 | First create `buildSrc`, if you don't. `build.gradle` will contains: 353 | 354 | ```groovy 355 | plugins { 356 | id("org.jetbrains.kotlin.jvm") version "1.6.10" 357 | } 358 | 359 | repositories { 360 | mavenCentral() 361 | google() 362 | gradlePluginPortal() 363 | 364 | maven("https://jitpack.io") 365 | } 366 | 367 | dependencies { 368 | implementation("com.android.tools.build:gradle:7.0.0") 369 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21") 370 | implementation("dev.icerock.moko:kswift-gradle-plugin:0.7.0") 371 | } 372 | ``` 373 | 374 | Then in `buildSrc/src/main/kotlin` create `MyKSwiftGenerator`: 375 | 376 | ```kotlin 377 | import dev.icerock.moko.kswift.plugin.context.ClassContext 378 | import dev.icerock.moko.kswift.plugin.feature.ProcessorContext 379 | import dev.icerock.moko.kswift.plugin.feature.ProcessorFeature 380 | import dev.icerock.moko.kswift.plugin.feature.BaseConfig 381 | import dev.icerock.moko.kswift.plugin.feature.Filter 382 | import io.outfoxx.swiftpoet.DeclaredTypeName 383 | import io.outfoxx.swiftpoet.ExtensionSpec 384 | import io.outfoxx.swiftpoet.FileSpec 385 | import kotlin.reflect.KClass 386 | 387 | 388 | class MyKSwiftGenerator( 389 | override val featureContext: KClass, 390 | override val filter: Filter 391 | ) : ProcessorFeature() { 392 | override fun doProcess(featureContext: ClassContext, processorContext: ProcessorContext) { 393 | val fileSpec: FileSpec.Builder = processorContext.fileSpecBuilder 394 | val frameworkName: String = processorContext.framework.baseName 395 | 396 | val classSimpleName = featureContext.clazz.name.substringAfterLast('/') 397 | 398 | fileSpec.addExtension( 399 | ExtensionSpec 400 | .builder( 401 | DeclaredTypeName.typeName("$frameworkName.$classSimpleName") 402 | ) 403 | .build() 404 | ) 405 | } 406 | 407 | class Config : BaseConfig { 408 | override var filter: Filter = Filter.Exclude(emptySet()) 409 | } 410 | 411 | companion object : Factory { 412 | override fun create(block: Config.() -> Unit): MyKSwiftGenerator { 413 | val config = Config().apply(block) 414 | return MyKSwiftGenerator(featureContext, config.filter) 415 | } 416 | 417 | override val featureContext: KClass = ClassContext::class 418 | 419 | @JvmStatic 420 | override val factory = Companion 421 | } 422 | } 423 | ``` 424 | 425 | in this example will be generated swift extension for each class in kotlin module. You can select 426 | required `Context` to got required info from klib metadata. 427 | 428 | last step - enable feature in gradle: 429 | 430 | kotlin: 431 | ```kotlin 432 | kswift { 433 | install(MyKSwiftGenerator) 434 | } 435 | ``` 436 | 437 | groovy: 438 | ```groovy 439 | kswift { 440 | install(MyKSwiftGenerator.factory) 441 | } 442 | ``` 443 | 444 | ## Set iOS deployment target for podspec 445 | 446 | kotlin: 447 | ```kotlin 448 | kswift { 449 | iosDeploymentTarget.set("11.0") 450 | } 451 | ``` 452 | 453 | groovy: 454 | ```groovy 455 | kswift { 456 | iosDeploymentTarget = "11.0" 457 | } 458 | ``` 459 | 460 | # Samples 461 | 462 | - [branch regular-framework](https://github.com/Alex009/moko-kswift-usage-sample/tree/regular-framework) - how to use KSwift without cocoapods 463 | - [branch cocoapods](https://github.com/Alex009/moko-kswift-usage-sample/tree/cocoapods) - how to use KSwift with JetBrains CocoaPods plugin 464 | 465 | # FAQ 466 | 467 | ## Where destination directory for all generated sources? 468 | 469 | Swift source code generates in same directory where compiles Kotlin/Native framework. In common case 470 | it directory 471 | `build/bin/{iosArm64 || iosX64}/{debugFramework || releaseFramework}/{frameworkName}Swift`. 472 | 473 | Kotlin/Native cocoapods plugin (and also mobile-multiplatform cocoapods plugin by IceRock) will move 474 | this sources into fixed directory - `build/cocoapods/framework/{frameworkName}Swift`. 475 | 476 | ## How to exclude generation of entries from some libraries? 477 | 478 | ```groovy 479 | kswift { 480 | excludeLibrary("{libraryName}") 481 | } 482 | ``` 483 | 484 | ## How to generate entries only from specific libraries? 485 | 486 | ```groovy 487 | kswift { 488 | includeLibrary("{libraryName1}") 489 | includeLibrary("{libraryName2}") 490 | } 491 | ``` 492 | 493 | # Samples 494 | 495 | More examples can be found in the [sample directory](sample). 496 | 497 | # Set Up Locally 498 | 499 | Clone project and just open it. Gradle plugin attached to sample by gradle composite build, so you 500 | will see changes at each gradle build. 501 | 502 | ```bash 503 | # clone repo 504 | git clone git@github.com:icerockdev/moko-kswift.git 505 | cd moko-kswift 506 | # generate podspec files for cocopods intergration. with integration will be generated swift files for cocoapod 507 | ./gradlew kSwiftmpp_library_podsPodspec 508 | ./gradlew kSwiftMultiPlatformLibraryPodspec 509 | # go to ios dir 510 | cd sample/ios-app 511 | # install pods 512 | pod install 513 | # now we can open xcworkspace and build ios project 514 | open ios-app.xcworkspace 515 | # or run xcodebuild 516 | xcodebuild -scheme ios-app -workspace ios-app.xcworkspace test -destination "platform=iOS Simulator,name=iPhone 12 mini" 517 | xcodebuild -scheme pods-test -workspace ios-app.xcworkspace test -destination "platform=iOS Simulator,name=iPhone 12 mini" 518 | ``` 519 | 520 | # Contributing 521 | 522 | All development (both new features and bug fixes) is performed in `develop` branch. This 523 | way `master` sources always contain sources of the most recently released version. Please send PRs 524 | with bug fixes to `develop` branch. Fixes to documentation in markdown files are an exception to 525 | this rule. They are updated directly in `master`. 526 | 527 | The `develop` branch is pushed to `master` during release. 528 | 529 | More detailed guide for contributers see in [contributing guide](CONTRIBUTING.md). 530 | 531 | # License 532 | 533 | Copyright 2021 IceRock MAG Inc 534 | 535 | Licensed under the Apache License, Version 2.0 (the "License"); 536 | you may not use this file except in compliance with the License. 537 | You may obtain a copy of the License at 538 | 539 | http://www.apache.org/licenses/LICENSE-2.0 540 | 541 | Unless required by applicable law or agreed to in writing, software 542 | distributed under the License is distributed on an "AS IS" BASIS, 543 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 544 | See the License for the specific language governing permissions and 545 | limitations under the License. 546 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | buildscript { 6 | repositories { 7 | mavenCentral() 8 | google() 9 | gradlePluginPortal() 10 | } 11 | 12 | dependencies { 13 | classpath(libs.mobileMultiplatformGradlePlugin) 14 | classpath(libs.kotlinGradlePlugin) 15 | classpath(libs.mokoGradlePlugin) 16 | classpath(libs.androidGradlePlugin) 17 | classpath(libs.detektGradlePlugin) 18 | classpath("dev.icerock.moko:kswift-gradle-plugin") 19 | } 20 | } 21 | 22 | apply(plugin = "dev.icerock.moko.gradle.publication.nexus") 23 | val mokoVersion = libs.versions.mokoKSwiftVersion.get() 24 | allprojects { 25 | group = "dev.icerock.moko" 26 | version = mokoVersion 27 | } 28 | 29 | // temporary fix for Apple Silicon (remove after 1.6.20 update) 30 | rootProject.plugins.withType { 31 | rootProject.the().nodeVersion = "16.0.0" 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m 2 | org.gradle.configureondemand=false 3 | org.gradle.parallel=true 4 | 5 | kotlin.code.style=official 6 | kotlin.native.enableDependencyPropagation=false 7 | kotlin.mpp.enableGranularSourceSetsMetadata=true 8 | kotlin.mpp.enableCompatibilityMetadataVariant=true 9 | 10 | android.useAndroidX=true 11 | 12 | mobile.multiplatform.iosTargetWarning=false 13 | 14 | moko.android.targetSdk=31 15 | moko.android.compileSdk=31 16 | moko.android.minSdk=16 17 | 18 | moko.publish.name=MOKO KSwift 19 | moko.publish.description=Swift-friendly api generator for Kotlin/Native frameworks 20 | moko.publish.repo.org=icerockdev 21 | moko.publish.repo.name=moko-kswift 22 | moko.publish.license=Apache-2.0 23 | moko.publish.developers=alex009|Aleksey Mikhailov|Aleksey.Mikhailov@icerockdev.com -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlinVersion = "1.6.10" 3 | androidAppCompatVersion = "1.2.0" 4 | materialDesignVersion = "1.0.0" 5 | androidLifecycleVersion = "2.1.0" 6 | androidCoreTestingVersion = "2.1.0" 7 | coroutinesVersion = "1.6.0-native-mt" 8 | mokoTestVersion = "0.4.0" 9 | mokoKSwiftVersion = "0.7.0" 10 | 11 | mokoMvvmVersion = "0.11.0" 12 | mokoResourcesVersion = "0.16.2" 13 | 14 | kotlinxMetadataKLibVersion = "0.0.1" 15 | 16 | kotlinPoetVersion = "1.6.0" 17 | swiftPoetVersion = "1.5.0" 18 | 19 | [libraries] 20 | appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" } 21 | material = { module = "com.google.android.material:material", version.ref = "materialDesignVersion" } 22 | lifecycle = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "androidLifecycleVersion" } 23 | coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesVersion" } 24 | mokoTest = { module = "dev.icerock.moko:test-core", version.ref = "mokoTestVersion" } 25 | 26 | mokoMvvmCore = { module = "dev.icerock.moko:mvvm-core" , version.ref = "mokoMvvmVersion" } 27 | mokoMvvmLiveData = { module = "dev.icerock.moko:mvvm-livedata" , version.ref = "mokoMvvmVersion" } 28 | mokoMvvmState = { module = "dev.icerock.moko:mvvm-state" , version.ref = "mokoMvvmVersion" } 29 | 30 | mokoResources = { module = "dev.icerock.moko:resources" , version.ref = "mokoResourcesVersion" } 31 | 32 | kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoetVersion" } 33 | swiftPoet = { module = "io.outfoxx:swiftpoet", version.ref = "swiftPoetVersion" } 34 | 35 | kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlinVersion" } 36 | kotlinxMetadataKLib = { module = "org.jetbrains.kotlinx:kotlinx-metadata-klib", version.ref = "kotlinxMetadataKLibVersion" } 37 | kotlinTestJUnit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinVersion" } 38 | 39 | # gradle plugins 40 | kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion" } 41 | # we use next version of kotlin gradle plugin in gradle plugin api to support 1.6 and 1.7 both 42 | kotlinGradlePluginNext = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version = "1.7.0" } 43 | mokoGradlePlugin = { module = "dev.icerock.moko:moko-gradle-plugin", version = "0.2.0" } 44 | mobileMultiplatformGradlePlugin = { module = "dev.icerock:mobile-multiplatform", version = "0.14.1" } 45 | androidGradlePlugin = { module = "com.android.tools.build:gradle", version = "7.0.4" } 46 | detektGradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version = "1.15.0" } 47 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-kswift/cccacec4041b52084c6f1200ce25e99ebc55818d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.publication") 7 | id("dev.icerock.moko.gradle.detekt") 8 | id("dev.icerock.moko.gradle.jvm") 9 | id("dev.icerock.moko.gradle.tests") 10 | id("com.gradle.plugin-publish") version ("0.15.0") 11 | id("java-gradle-plugin") 12 | } 13 | 14 | group = "dev.icerock.moko" 15 | version = libs.versions.mokoKSwiftVersion.get() 16 | 17 | dependencies { 18 | compileOnly(libs.kotlinGradlePluginNext) 19 | 20 | implementation(gradleKotlinDsl()) 21 | implementation(libs.kotlinCompilerEmbeddable) 22 | 23 | api(libs.swiftPoet) 24 | api(libs.kotlinxMetadataKLib) 25 | 26 | testImplementation(libs.kotlinTestJUnit) 27 | } 28 | 29 | gradlePlugin { 30 | plugins { 31 | create("kswift") { 32 | id = "dev.icerock.moko.kswift" 33 | implementationClass = "dev.icerock.moko.kswift.plugin.KSwiftPlugin" 34 | } 35 | } 36 | } 37 | 38 | pluginBundle { 39 | website = "https://github.com/icerockdev/moko-kswift" 40 | vcsUrl = "https://github.com/icerockdev/moko-kswift" 41 | description = "Swift-friendly api generator for Kotlin/Native frameworks" 42 | tags = listOf("moko-kswift", "moko", "kotlin", "kotlin-multiplatform", "codegen", "swift") 43 | 44 | plugins { 45 | getByName("kswift") { 46 | displayName = "MOKO KSwift generator plugin" 47 | } 48 | } 49 | 50 | mavenCoordinates { 51 | groupId = project.group as String 52 | artifactId = project.name 53 | version = project.version as String 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | moko.publish.name=MOKO KSwift 4 | moko.publish.description=Swift-friendly api generator for Kotlin/Native frameworks 5 | moko.publish.repo.org=icerockdev 6 | moko.publish.repo.name=moko-kswift 7 | moko.publish.license=Apache-2.0 8 | moko.publish.developers=alex009|Aleksey Mikhailov|Aleksey.Mikhailov@icerockdev.com -------------------------------------------------------------------------------- /kswift-gradle-plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | enableFeaturePreview("VERSION_CATALOGS") 6 | 7 | rootProject.name = "kswift-gradle-plugin" 8 | 9 | pluginManagement { 10 | repositories { 11 | mavenCentral() 12 | google() 13 | 14 | gradlePluginPortal() 15 | } 16 | 17 | resolutionStrategy { 18 | eachPlugin { 19 | if(requested.id.id.startsWith("dev.icerock.moko.gradle")) { 20 | // FIXME use single source of truth 21 | useModule("dev.icerock.moko:moko-gradle-plugin:0.2.0") 22 | } 23 | } 24 | } 25 | } 26 | 27 | dependencyResolutionManagement { 28 | repositories { 29 | mavenCentral() 30 | google() 31 | } 32 | 33 | versionCatalogs { 34 | create("libs") { 35 | from(files("../gradle/libs.versions.toml")) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KLibProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import dev.icerock.moko.kswift.plugin.context.FeatureContext 8 | import dev.icerock.moko.kswift.plugin.context.LibraryContext 9 | import dev.icerock.moko.kswift.plugin.feature.ProcessorContext 10 | import dev.icerock.moko.kswift.plugin.feature.ProcessorFeature 11 | import io.outfoxx.swiftpoet.FileSpec 12 | import kotlinx.metadata.klib.KlibModuleMetadata 13 | import org.gradle.api.logging.Logger 14 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 15 | import java.io.File 16 | import kotlin.reflect.KClass 17 | 18 | class KLibProcessor( 19 | private val logger: Logger, 20 | private val extension: KSwiftExtension 21 | ) { 22 | private val features: Map, List>> 23 | get() = extension.features.groupBy { it.featureContext } 24 | 25 | fun processFeatureContext(library: File, outputDir: File, framework: Framework) { 26 | @Suppress("TooGenericExceptionCaught") 27 | val metadata: KlibModuleMetadata = try { 28 | KotlinMetadataLibraryProvider.readLibraryMetadata(library) 29 | } catch (exc: IllegalStateException) { 30 | logger.info("library can't be read", exc) 31 | return 32 | } catch (exc: Exception) { 33 | logger.error("can't parse metadata", exc) 34 | return 35 | } 36 | 37 | val fileName: String = metadata.name 38 | .replace("[<>]".toRegex(), "") 39 | .replace("[.:]".toRegex(), "_") 40 | val fileSpecBuilder: FileSpec.Builder = FileSpec.builder(fileName) 41 | 42 | val processorContext = ProcessorContext( 43 | fileSpecBuilder = fileSpecBuilder, 44 | framework = framework 45 | ) 46 | 47 | val libraryContext = LibraryContext(metadata) 48 | libraryContext.visit { featureContext -> 49 | logger.info("visit ${featureContext.prefixedUniqueId} - $featureContext") 50 | processFeatureContext(featureContext, processorContext) 51 | } 52 | 53 | val fileSpec: FileSpec = fileSpecBuilder 54 | .addComment( 55 | "This file automatically generated by MOKO KSwift (https://github.com/icerockdev/moko-kswift)\n" 56 | ) 57 | .build() 58 | if (fileSpec.members.isNotEmpty()) { 59 | fileSpec.writeTo(outputDir) 60 | } 61 | } 62 | 63 | private fun processFeatureContext( 64 | featureContext: T, 65 | processorContext: ProcessorContext 66 | ) { 67 | val kclass: KClass = featureContext::class 68 | 69 | @Suppress("UNCHECKED_CAST") 70 | val processors: List> = 71 | features[kclass].orEmpty() as List> 72 | 73 | processors.forEach { featureProcessor -> 74 | @Suppress("TooGenericExceptionCaught") 75 | try { 76 | featureProcessor.process(featureContext, processorContext) 77 | } catch (exc: Exception) { 78 | logger.info("can't process context $featureContext", exc) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KSwiftExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import dev.icerock.moko.kswift.plugin.context.FeatureContext 8 | import dev.icerock.moko.kswift.plugin.feature.BaseConfig 9 | import dev.icerock.moko.kswift.plugin.feature.ProcessorFeature 10 | import org.gradle.api.DomainObjectSet 11 | import org.gradle.api.provider.Property 12 | 13 | abstract class KSwiftExtension { 14 | abstract val features: DomainObjectSet> 15 | 16 | abstract val excludedLibs: DomainObjectSet 17 | abstract val includedLibs: DomainObjectSet 18 | 19 | abstract val projectPodspecName: Property 20 | abstract val iosDeploymentTarget: Property 21 | 22 | fun > install( 23 | featureFactory: ProcessorFeature.Factory 24 | ) { 25 | features.add(featureFactory.create { }) 26 | } 27 | 28 | fun > install( 29 | featureFactory: ProcessorFeature.Factory, 30 | config: Config.() -> Unit 31 | ) { 32 | features.add(featureFactory.create(config)) 33 | } 34 | 35 | fun includeLibrary(libraryName: String) { 36 | includedLibs.add(libraryName) 37 | } 38 | 39 | fun excludeLibrary(libraryName: String) { 40 | excludedLibs.add(libraryName) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KSwiftPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import org.gradle.kotlin.dsl.create 10 | import org.gradle.kotlin.dsl.getByType 11 | import org.gradle.kotlin.dsl.withType 12 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 13 | import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper 14 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 15 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 16 | import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink 17 | 18 | @Suppress("unused") 19 | open class KSwiftPlugin : Plugin { 20 | override fun apply(target: Project) { 21 | val extension = target.extensions.create("kswift") 22 | val podspecNameDefault = target.name.replace('-', '_') 23 | extension.projectPodspecName.set(podspecNameDefault) 24 | 25 | val processor = KLibProcessor( 26 | logger = target.logger, 27 | extension = extension 28 | ) 29 | 30 | target.plugins 31 | .withType() 32 | .configureEach { pluginWrapper -> 33 | val multiplatformExtension = target.extensions 34 | .getByType(pluginWrapper.projectExtensionClass) 35 | 36 | applyToKotlinMultiplatform(multiplatformExtension, processor, extension) 37 | } 38 | } 39 | 40 | private fun applyToKotlinMultiplatform( 41 | extension: KotlinMultiplatformExtension, 42 | processor: KLibProcessor, 43 | kSwiftExtension: KSwiftExtension 44 | ) { 45 | extension.targets 46 | .withType() 47 | .matching { it.konanTarget.family.isAppleFamily } 48 | .configureEach { applyToAppleTarget(it, processor, kSwiftExtension) } 49 | } 50 | 51 | private fun applyToAppleTarget( 52 | target: KotlinNativeTarget, 53 | processor: KLibProcessor, 54 | kSwiftExtension: KSwiftExtension 55 | ) { 56 | target.binaries 57 | .withType() 58 | .configureEach { applyToAppleFramework(it, processor, kSwiftExtension) } 59 | } 60 | 61 | private fun applyToAppleFramework( 62 | framework: Framework, 63 | processor: KLibProcessor, 64 | kSwiftExtension: KSwiftExtension 65 | ) { 66 | val linkTask: KotlinNativeLink = framework.linkTask 67 | linkTask.doLast(PostProcessLinkTask(framework, processor, kSwiftExtension)) 68 | registerPodspecTask(linkTask, kSwiftExtension) 69 | } 70 | 71 | private fun registerPodspecTask( 72 | linkTask: KotlinNativeLink, 73 | kSwiftExtension: KSwiftExtension 74 | ) { 75 | val project: Project = linkTask.project 76 | val frameworkName: String = linkTask.baseName 77 | val podspecTaskName = "kSwift${frameworkName}Podspec" 78 | 79 | if (project.tasks.findByName(podspecTaskName) != null) return 80 | 81 | project.tasks.create(podspecTaskName, KSwiftPodspecTask::class) { 82 | it.linkTask = linkTask 83 | it.kSwiftExtension = kSwiftExtension 84 | it.dependsOn(linkTask) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KSwiftPodspecTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import org.gradle.api.DefaultTask 8 | import org.gradle.api.tasks.InputDirectory 9 | import org.gradle.api.tasks.Internal 10 | import org.gradle.api.tasks.OutputDirectory 11 | import org.gradle.api.tasks.OutputFile 12 | import org.gradle.api.tasks.TaskAction 13 | import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink 14 | import java.io.File 15 | 16 | open class KSwiftPodspecTask : DefaultTask() { 17 | 18 | init { 19 | group = "cocoapods" 20 | } 21 | 22 | @get:Internal 23 | internal lateinit var linkTask: KotlinNativeLink 24 | 25 | @get:Internal 26 | internal lateinit var kSwiftExtension: KSwiftExtension 27 | 28 | private val projectPodspecName: String 29 | get() = kSwiftExtension.projectPodspecName.get() 30 | 31 | private val iosDeploymentTarget: String? 32 | get() = kSwiftExtension.iosDeploymentTarget.orNull 33 | 34 | private val moduleName: String 35 | get() = "${linkTask.baseName}Swift" 36 | 37 | private val podspecName: String 38 | get() = "${projectPodspecName}Swift" 39 | 40 | @get:Internal 41 | val outputCocoapodsDir: File 42 | get() = File(project.buildDir, "cocoapods/framework") 43 | 44 | @get:InputDirectory 45 | val frameworkDir: File 46 | get() = linkTask.outputFile.get() 47 | 48 | @get:OutputDirectory 49 | val outputDir: File 50 | get() = File(outputCocoapodsDir, moduleName) 51 | 52 | @get:OutputFile 53 | val outputPodspec: File 54 | get() = File(project.projectDir, "$podspecName.podspec") 55 | 56 | @TaskAction 57 | fun execute() { 58 | frameworkDir.copyRecursively(outputCocoapodsDir, true) 59 | 60 | val isStatic: Boolean = linkTask.isStaticFramework 61 | val iosDeploymentString: String = iosDeploymentTarget?.let { 62 | "spec.ios.deployment_target = '$it'" 63 | }.orEmpty() 64 | 65 | @Suppress("MaxLineLength") 66 | outputPodspec.writeText( 67 | """ 68 | Pod::Spec.new do |spec| 69 | spec.name = '$podspecName' 70 | spec.version = '1.0' 71 | spec.homepage = 'Link to a Kotlin/Native module homepage' 72 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 73 | spec.authors = '' 74 | spec.license = '' 75 | spec.summary = 'Some description for a Kotlin/Native module' 76 | spec.module_name = "$moduleName" 77 | 78 | $iosDeploymentString 79 | spec.static_framework = $isStatic 80 | spec.dependency '$projectPodspecName' 81 | spec.source_files = "build/cocoapods/framework/$moduleName/**/*.{h,m,swift}" 82 | end 83 | """.trimIndent() 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KSwiftRuntimeAnnotations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | enum class KSwiftRuntimeAnnotations(val className: String) { 8 | KSWIFT_INCLUDE("dev/icerock/moko/kswift/KSwiftInclude"), 9 | KSWIFT_EXCLUDE("dev/icerock/moko/kswift/KSwiftExclude"), 10 | KSWIFT_OVERRIDE_NAME("dev/icerock/moko/kswift/KSwiftOverrideName") 11 | } 12 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KmAnnotationExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.KmAnnotationArgument 9 | 10 | fun List.findByClassName(annotation: KSwiftRuntimeAnnotations): KmAnnotation? { 11 | return findByClassName(annotation.className) 12 | } 13 | 14 | fun List.findByClassName(className: String): KmAnnotation? { 15 | return firstOrNull { it.className == className } 16 | } 17 | 18 | fun KmAnnotation.getStringArgument(name: String): String? { 19 | return (arguments[name] as? KmAnnotationArgument.StringValue)?.value 20 | } 21 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KmClassExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import io.outfoxx.swiftpoet.ANY_OBJECT 8 | import io.outfoxx.swiftpoet.DeclaredTypeName 9 | import io.outfoxx.swiftpoet.TypeName 10 | import io.outfoxx.swiftpoet.TypeVariableName 11 | import io.outfoxx.swiftpoet.parameterizedBy 12 | import kotlinx.metadata.ClassName 13 | import kotlinx.metadata.Flag 14 | import kotlinx.metadata.KmClass 15 | import kotlinx.metadata.KmConstructor 16 | 17 | fun KmClass.isDataClass(): Boolean { 18 | return Flag.Class.IS_DATA(flags) 19 | } 20 | 21 | fun KmClass.getPrimaryConstructor(): KmConstructor = constructors.getPrimaryConstructor() 22 | 23 | fun KmClass.buildTypeVariableNames( 24 | kotlinFrameworkName: String 25 | ) = this.typeParameters.map { typeParam -> 26 | val bounds: List = typeParam.upperBounds 27 | .map { it.toTypeName(kotlinFrameworkName, isUsedInGenerics = true) } 28 | .map { TypeVariableName.Bound(it) } 29 | .ifEmpty { listOf(TypeVariableName.Bound(ANY_OBJECT)) } 30 | TypeVariableName.typeVariable(typeParam.name, bounds) 31 | } 32 | 33 | fun KmClass.getDeclaredTypeNameWithGenerics( 34 | kotlinFrameworkName: String, 35 | classes: List 36 | ): TypeName { 37 | val typeVariables: List = buildTypeVariableNames(kotlinFrameworkName) 38 | val haveGenerics: Boolean = typeVariables.isNotEmpty() 39 | val isInterface: Boolean = Flag.Class.IS_INTERFACE(flags) 40 | 41 | @Suppress("SpreadOperator") 42 | return getDeclaredTypeName(kotlinFrameworkName, classes) 43 | .let { type -> 44 | if (haveGenerics.not() || isInterface) type 45 | else type.parameterizedBy(*typeVariables.toTypedArray()) 46 | } 47 | } 48 | 49 | fun KmClass.getDeclaredTypeName( 50 | kotlinFrameworkName: String, 51 | classes: List 52 | ): DeclaredTypeName { 53 | return DeclaredTypeName( 54 | moduleName = kotlinFrameworkName, 55 | simpleName = getSimpleName( 56 | className = name, 57 | classes = classes 58 | ) 59 | ) 60 | } 61 | 62 | fun getSimpleName(className: ClassName, classes: List): String { 63 | val path = className.substringBeforeLast('/') 64 | val nameWithDots = className.substringAfterLast('/') 65 | val parts = nameWithDots.split(".") 66 | 67 | val partsInfo = parts.mapIndexed { idx, name -> 68 | val classInfo = classes.first { 69 | it.name == path + "/" + parts.take(idx + 1).joinToString(".") 70 | } 71 | val isInterface: Boolean = Flag.Class.IS_INTERFACE(classInfo.flags) 72 | val haveGenerics: Boolean = classInfo.typeParameters.isNotEmpty() 73 | val validForNesting = !isInterface && !haveGenerics 74 | validForNesting 75 | } 76 | 77 | var lastPartValid = partsInfo.first() 78 | val indexOfFirstDot: Int = partsInfo.drop(1).indexOfFirst { partValid -> 79 | val valid = lastPartValid && partValid 80 | if (!valid) lastPartValid = partValid 81 | valid 82 | } 83 | 84 | return if (indexOfFirstDot == -1) { 85 | nameWithDots.replace(".", "") 86 | } else { 87 | val left = parts.take(indexOfFirstDot + 1).joinToString("") 88 | val right = parts.drop(indexOfFirstDot + 1).joinToString("") 89 | "$left.$right" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KmConstructorExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import kotlinx.metadata.Flag 8 | import kotlinx.metadata.KmConstructor 9 | 10 | fun KmConstructor.isPrimary(): Boolean = Flag.Constructor.IS_SECONDARY(flags).not() 11 | 12 | fun List.getPrimaryConstructor(): KmConstructor = single { constructor -> constructor.isPrimary() } 13 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KmTypeExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import io.outfoxx.swiftpoet.ANY_OBJECT 8 | import io.outfoxx.swiftpoet.ARRAY 9 | import io.outfoxx.swiftpoet.BOOL 10 | import io.outfoxx.swiftpoet.DICTIONARY 11 | import io.outfoxx.swiftpoet.DeclaredTypeName 12 | import io.outfoxx.swiftpoet.FunctionTypeName 13 | import io.outfoxx.swiftpoet.INT32 14 | import io.outfoxx.swiftpoet.ParameterSpec 15 | import io.outfoxx.swiftpoet.SET 16 | import io.outfoxx.swiftpoet.STRING 17 | import io.outfoxx.swiftpoet.TypeName 18 | import io.outfoxx.swiftpoet.TypeVariableName 19 | import io.outfoxx.swiftpoet.UINT64 20 | import io.outfoxx.swiftpoet.VOID 21 | import io.outfoxx.swiftpoet.parameterizedBy 22 | import kotlinx.metadata.ClassName 23 | import kotlinx.metadata.Flag 24 | import kotlinx.metadata.KmClassifier 25 | import kotlinx.metadata.KmType 26 | import kotlinx.metadata.KmTypeProjection 27 | 28 | @Suppress("ReturnCount") 29 | fun KmType.toTypeName( 30 | moduleName: String, 31 | isUsedInGenerics: Boolean = false, 32 | typeVariables: Map = emptyMap(), 33 | removeTypeVariables: Boolean = false 34 | ): TypeName { 35 | return when (val classifier = classifier) { 36 | is KmClassifier.TypeParameter -> { 37 | val typeVariable: TypeVariableName? = typeVariables[classifier.id] 38 | if (typeVariable != null) { 39 | return if (!removeTypeVariables) typeVariable 40 | else typeVariable.bounds.firstOrNull()?.type ?: ANY_OBJECT 41 | } else throw IllegalArgumentException("can't read type parameter $this without type variables list") 42 | } 43 | is KmClassifier.TypeAlias -> { 44 | classifier.name.kotlinTypeNameToSwift(moduleName, isUsedInGenerics, arguments) 45 | ?: throw IllegalArgumentException("can't read type alias $this") 46 | } 47 | is KmClassifier.Class -> { 48 | val name: TypeName? = 49 | classifier.name.kotlinTypeNameToSwift(moduleName, isUsedInGenerics, arguments) 50 | return name ?: kotlinTypeToTypeName( 51 | moduleName, 52 | classifier.name, 53 | typeVariables, 54 | removeTypeVariables 55 | ) 56 | } 57 | } 58 | } 59 | 60 | @Suppress("LongMethod", "ComplexMethod") 61 | fun String.kotlinTypeNameToSwift( 62 | moduleName: String, 63 | isUsedInGenerics: Boolean, 64 | arguments: MutableList 65 | ): TypeName? { 66 | return when (this) { 67 | "kotlin/String" -> if (isUsedInGenerics) { 68 | DeclaredTypeName(moduleName = "Foundation", simpleName = "NSString") 69 | } else { 70 | STRING 71 | } 72 | "kotlin/Int" -> if (isUsedInGenerics) { 73 | DeclaredTypeName(moduleName = moduleName, simpleName = "KotlinInt") 74 | } else { 75 | INT32 76 | } 77 | "kotlin/Boolean" -> if (isUsedInGenerics) { 78 | DeclaredTypeName(moduleName = moduleName, simpleName = "KotlinBoolean") 79 | } else { 80 | BOOL 81 | } 82 | "kotlin/ULong" -> UINT64 83 | "kotlin/Unit" -> VOID 84 | "kotlin/Any" -> ANY_OBJECT 85 | "kotlin/collections/List" -> { 86 | arguments.first().type?.run { 87 | DeclaredTypeName.typeName(ARRAY.name).parameterizedBy( 88 | this.toTypeName( 89 | moduleName, 90 | isUsedInGenerics = this.shouldUseKotlinTypeWhenHandlingCollections() 91 | ) 92 | ) 93 | } 94 | } 95 | "kotlin/collections/Set" -> { 96 | arguments.first().type?.run { 97 | DeclaredTypeName.typeName(SET.name).parameterizedBy( 98 | this.toTypeName( 99 | moduleName, 100 | isUsedInGenerics = this.shouldUseKotlinTypeWhenHandlingCollections() 101 | ) 102 | ) 103 | } 104 | } 105 | "kotlin/collections/Map" -> { 106 | val firstArgumentType = arguments.first().type 107 | val secondArgumentType = arguments[1].type 108 | if (firstArgumentType != null && secondArgumentType != null) { 109 | DeclaredTypeName.typeName(DICTIONARY.name).parameterizedBy( 110 | firstArgumentType.toTypeName( 111 | moduleName, 112 | isUsedInGenerics = firstArgumentType.shouldUseKotlinTypeWhenHandlingCollections() 113 | ), 114 | secondArgumentType.toTypeName( 115 | moduleName, 116 | isUsedInGenerics = secondArgumentType.shouldUseKotlinTypeWhenHandlingCollections() 117 | ) 118 | ) 119 | } else { 120 | null 121 | } 122 | } 123 | else -> { 124 | if (this.startsWith("platform/")) { 125 | val withoutCompanion: String = this.removeSuffix(".Companion") 126 | val moduleAndClass: List = withoutCompanion.split("/").drop(1) 127 | val module: String = moduleAndClass[0] 128 | val className: String = moduleAndClass[1] 129 | 130 | DeclaredTypeName.typeName( 131 | listOf(module, className).joinToString(".") 132 | ).objcNameToSwift() 133 | } else if (this.startsWith("kotlin/Function")) { 134 | null 135 | } else if (this.startsWith("kotlin/") && this.count { it == '/' } == 1) { 136 | DeclaredTypeName( 137 | moduleName = moduleName, 138 | simpleName = "Kotlin" + this.split("/").last() 139 | ) 140 | } else null 141 | } 142 | } 143 | } 144 | 145 | fun KmType.kotlinTypeToTypeName( 146 | moduleName: String, 147 | classifierName: ClassName, 148 | typeVariables: Map, 149 | removeTypeVariables: Boolean 150 | ): TypeName { 151 | val typeName = DeclaredTypeName( 152 | moduleName = moduleName, 153 | simpleName = classifierName.split("/").last() 154 | ) 155 | if (this.arguments.isEmpty()) return typeName 156 | 157 | @Suppress("UnsafeCallOnNullableType") 158 | return when (classifierName) { 159 | "kotlin/Function1" -> { 160 | val inputType: TypeName = arguments[0].type?.toTypeName( 161 | moduleName = moduleName, 162 | isUsedInGenerics = false, 163 | typeVariables = typeVariables, 164 | removeTypeVariables = removeTypeVariables 165 | )!! 166 | val outputType: TypeName = arguments[1].type?.toTypeName( 167 | moduleName = moduleName, 168 | isUsedInGenerics = false, 169 | typeVariables = typeVariables, 170 | removeTypeVariables = removeTypeVariables 171 | )!! 172 | FunctionTypeName.get( 173 | parameters = listOf(ParameterSpec.unnamed(inputType)), 174 | returnType = outputType 175 | ) 176 | } 177 | else -> { 178 | val arguments: List = this.arguments.mapNotNull { typeProj -> 179 | typeProj.type?.toTypeName( 180 | moduleName = moduleName, 181 | isUsedInGenerics = true, 182 | typeVariables = typeVariables, 183 | removeTypeVariables = removeTypeVariables 184 | ) 185 | } 186 | @Suppress("SpreadOperator") 187 | typeName.parameterizedBy(*arguments.toTypedArray()) 188 | } 189 | } 190 | } 191 | 192 | fun DeclaredTypeName.objcNameToSwift(): DeclaredTypeName { 193 | return when (moduleName) { 194 | "Foundation" -> peerType(simpleName.removePrefix("NS")) 195 | else -> this 196 | } 197 | } 198 | 199 | fun KmType.shouldUseKotlinTypeWhenHandlingOptionalType(): Boolean = 200 | if (classifier.toString().contains("kotlin/String")) { 201 | false 202 | } else { 203 | Flag.Type.IS_NULLABLE(flags) 204 | } 205 | 206 | fun KmType.shouldUseKotlinTypeWhenHandlingCollections(): Boolean = 207 | !classifier.toString().contains("kotlin/String") 208 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/KotlinMetadataLibraryProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import kotlinx.metadata.klib.KlibModuleMetadata 8 | import org.jetbrains.kotlin.library.KotlinLibrary 9 | import org.jetbrains.kotlin.library.resolveSingleFileKlib 10 | import java.io.File 11 | import org.jetbrains.kotlin.konan.file.File as KonanFile 12 | 13 | class KotlinMetadataLibraryProvider( 14 | private val library: KotlinLibrary 15 | ) : KlibModuleMetadata.MetadataLibraryProvider { 16 | override val moduleHeaderData: ByteArray 17 | get() = library.moduleHeaderData 18 | 19 | override fun packageMetadata(fqName: String, partName: String): ByteArray = 20 | library.packageMetadata(fqName, partName) 21 | 22 | override fun packageMetadataParts(fqName: String): Set = 23 | library.packageMetadataParts(fqName) 24 | 25 | companion object { 26 | fun readLibraryMetadata(libraryPath: File): KlibModuleMetadata { 27 | check(libraryPath.exists()) { "Library does not exist: $libraryPath" } 28 | 29 | val libraryKonanFile = KonanFile(libraryPath.absolutePath) 30 | val library = resolveSingleFileKlib(libraryKonanFile) 31 | 32 | return KlibModuleMetadata.read(KotlinMetadataLibraryProvider(library)) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/PostProcessLinkTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import org.gradle.api.Action 8 | import org.gradle.api.Task 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink 11 | import java.io.File 12 | 13 | internal class PostProcessLinkTask( 14 | private val framework: Framework, 15 | private val processor: KLibProcessor, 16 | private val kSwiftExtension: KSwiftExtension, 17 | ) : Action { 18 | 19 | override fun execute(task: Task) { 20 | val linkTask: KotlinNativeLink = task as KotlinNativeLink 21 | 22 | val kotlinFrameworkName = framework.baseName 23 | val swiftFrameworkName = "${kotlinFrameworkName}Swift" 24 | val outputDir = File(framework.outputDirectory, swiftFrameworkName) 25 | outputDir.deleteRecursively() 26 | 27 | linkTask.inputs.files 28 | .filter { it.extension == "klib" } 29 | .filter { file -> 30 | val name = file.nameWithoutExtension 31 | if (kSwiftExtension.includedLibs.isNotEmpty()) { 32 | if (kSwiftExtension.includedLibs.contains(name).not()) return@filter false 33 | } 34 | kSwiftExtension.excludedLibs.contains(name).not() 35 | } 36 | .forEach { library -> 37 | processor.processFeatureContext(library, outputDir, framework) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/ChildContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | abstract class ChildContext : FeatureContext() { 8 | abstract val parentContext: T 9 | } 10 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/ClassContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.KmClass 9 | import kotlinx.metadata.klib.annotations 10 | 11 | data class ClassContext( 12 | override val parentContext: FragmentContext, 13 | val clazz: KmClass 14 | ) : ChildContext() { 15 | override fun visitChildren(action: (FeatureContext) -> Unit) { 16 | // TODO 17 | } 18 | 19 | override fun toString(): String { 20 | return buildString { 21 | append("class name ") 22 | append(clazz.name) 23 | append(", parentContext ") 24 | append(parentContext.toString()) 25 | } 26 | } 27 | 28 | override val uniqueId: String 29 | get() = buildString { 30 | append(parentContext.uniqueId) 31 | append("/") 32 | append(clazz.name) 33 | } 34 | 35 | override val annotations: List 36 | get() = clazz.annotations 37 | } 38 | 39 | val ClassContext.kLibClasses: List 40 | get() = parentContext.parentContext.metadata.fragments.flatMap { it.classes } 41 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/FeatureContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | 9 | abstract class FeatureContext { 10 | fun visit(action: (FeatureContext) -> Unit) { 11 | action(this) 12 | visitChildren(action) 13 | } 14 | 15 | protected abstract fun visitChildren(action: (FeatureContext) -> Unit) 16 | 17 | abstract val uniqueId: String 18 | 19 | @Suppress("UnsafeCallOnNullableType") 20 | val prefixedUniqueId: String get() = this::class.simpleName!! + "/" + uniqueId 21 | 22 | abstract val annotations: List 23 | } 24 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/FragmentContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.KmModuleFragment 9 | import kotlinx.metadata.klib.moduleFragmentFiles 10 | 11 | data class FragmentContext( 12 | override val parentContext: LibraryContext, 13 | val fragment: KmModuleFragment 14 | ) : ChildContext() { 15 | override fun visitChildren(action: (FeatureContext) -> Unit) { 16 | fragment.pkg?.let { PackageContext(this, it).visit(action) } 17 | fragment.classes.forEach { ClassContext(this, it).visit(action) } 18 | } 19 | 20 | override fun toString(): String { 21 | return buildString { 22 | append("fragment files ") 23 | append(fragment.moduleFragmentFiles) 24 | append(", parentContext ") 25 | append(parentContext.toString()) 26 | } 27 | } 28 | 29 | override val uniqueId: String 30 | get() = parentContext.uniqueId 31 | 32 | override val annotations: List 33 | get() = emptyList() 34 | } 35 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/LibraryContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.klib.KlibModuleMetadata 9 | 10 | data class LibraryContext( 11 | val metadata: KlibModuleMetadata 12 | ) : FeatureContext() { 13 | override fun visitChildren(action: (FeatureContext) -> Unit) { 14 | metadata.fragments.forEach { FragmentContext(this, it).visit(action) } 15 | } 16 | 17 | override fun toString(): String { 18 | return buildString { 19 | append("metadata ") 20 | append(metadata.name) 21 | append(", fragments count ") 22 | append(metadata.fragments.size) 23 | append(", annotations count ") 24 | append(metadata.annotations.size) 25 | } 26 | } 27 | 28 | override val uniqueId: String 29 | get() = metadata.name.removeSurrounding("<", ">") 30 | 31 | override val annotations: List 32 | get() = metadata.annotations 33 | } 34 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/PackageContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.KmPackage 9 | import kotlinx.metadata.klib.fqName 10 | 11 | data class PackageContext( 12 | override val parentContext: FragmentContext, 13 | val pkg: KmPackage 14 | ) : ChildContext() { 15 | override fun visitChildren(action: (FeatureContext) -> Unit) { 16 | pkg.functions.forEach { PackageFunctionContext(this, it).visit(action) } 17 | } 18 | 19 | override fun toString(): String { 20 | return buildString { 21 | append("package name ") 22 | append(pkg.fqName) 23 | append(", parentContext ") 24 | append(parentContext.toString()) 25 | } 26 | } 27 | 28 | override val uniqueId: String 29 | get() = buildString { 30 | append(parentContext.uniqueId) 31 | append('/') 32 | append(pkg.fqName) 33 | } 34 | 35 | override val annotations: List 36 | get() = emptyList() 37 | } 38 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/context/PackageFunctionContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.context 6 | 7 | import kotlinx.metadata.KmAnnotation 8 | import kotlinx.metadata.KmClass 9 | import kotlinx.metadata.KmFunction 10 | import kotlinx.metadata.KmType 11 | import kotlinx.metadata.KmValueParameter 12 | import kotlinx.metadata.klib.annotations 13 | import kotlinx.metadata.klib.uniqId 14 | 15 | data class PackageFunctionContext( 16 | override val parentContext: PackageContext, 17 | val func: KmFunction 18 | ) : ChildContext() { 19 | override fun visitChildren(action: (FeatureContext) -> Unit) = Unit 20 | 21 | override fun toString(): String { 22 | return buildString { 23 | append("package function ") 24 | append(func.name) 25 | append(", uniqId ") 26 | append(func.uniqId) 27 | append(", parentContext ") 28 | append(parentContext.toString()) 29 | } 30 | } 31 | 32 | override val uniqueId: String 33 | get() = buildString { 34 | append(parentContext.uniqueId) 35 | append('/') 36 | append(func.receiverParameterType?.classifier) 37 | append('/') 38 | append(func.name) 39 | append('/') 40 | append(func.valueParameters.joinToString(",") { it.uniqueId }) 41 | } 42 | 43 | private val KmValueParameter.uniqueId: String 44 | get() = buildString { 45 | append(name) 46 | append(':') 47 | type?.also { type -> 48 | append(type.uniqueId) 49 | } 50 | } 51 | 52 | private val KmType.uniqueId: String 53 | get() = buildString { 54 | append(classifier) 55 | if (arguments.isNotEmpty()) { 56 | val args = arguments 57 | .mapNotNull { it.type?.uniqueId } 58 | .joinToString(separator = ",", prefix = "<", postfix = ">") 59 | append(args) 60 | } 61 | } 62 | 63 | override val annotations: List 64 | get() = func.annotations 65 | } 66 | 67 | val PackageFunctionContext.classes: List 68 | get() = parentContext.parentContext.parentContext.metadata.fragments.flatMap { it.classes } 69 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/BaseConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.context.FeatureContext 8 | 9 | interface BaseConfig { 10 | val filter: Filter 11 | 12 | fun excludeFilter(vararg names: String): Filter.Exclude { 13 | return Filter.Exclude(names.toSet()) 14 | } 15 | 16 | fun includeFilter(vararg names: String): Filter.Include { 17 | return Filter.Include(names.toSet()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/DataClassCopyFeature.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.buildTypeVariableNames 8 | import dev.icerock.moko.kswift.plugin.context.ClassContext 9 | import dev.icerock.moko.kswift.plugin.context.kLibClasses 10 | import dev.icerock.moko.kswift.plugin.getDeclaredTypeNameWithGenerics 11 | import dev.icerock.moko.kswift.plugin.getPrimaryConstructor 12 | import dev.icerock.moko.kswift.plugin.isDataClass 13 | import dev.icerock.moko.kswift.plugin.shouldUseKotlinTypeWhenHandlingOptionalType 14 | import dev.icerock.moko.kswift.plugin.toTypeName 15 | import io.outfoxx.swiftpoet.CodeBlock 16 | import io.outfoxx.swiftpoet.ExtensionSpec 17 | import io.outfoxx.swiftpoet.FunctionSpec 18 | import io.outfoxx.swiftpoet.FunctionTypeName 19 | import io.outfoxx.swiftpoet.ParameterSpec 20 | import io.outfoxx.swiftpoet.TypeSpec 21 | import io.outfoxx.swiftpoet.TypeVariableName 22 | import kotlinx.metadata.Flag 23 | import kotlinx.metadata.KmClass 24 | import kotlin.reflect.KClass 25 | 26 | /** 27 | * Creates an improved copy method for data classes 28 | */ 29 | 30 | class DataClassCopyFeature( 31 | override val featureContext: KClass, 32 | override val filter: Filter 33 | ) : ProcessorFeature() { 34 | 35 | @Suppress("ReturnCount") 36 | override fun doProcess(featureContext: ClassContext, processorContext: ProcessorContext) { 37 | 38 | val kotlinFrameworkName: String = processorContext.framework.baseName 39 | val kmClass: KmClass = featureContext.clazz 40 | 41 | if (Flag.IS_PUBLIC(kmClass.flags).not()) return 42 | 43 | if (kmClass.isDataClass().not()) return 44 | 45 | val typeVariables: List = 46 | kmClass.buildTypeVariableNames(kotlinFrameworkName) 47 | 48 | // doesn't support generic data classes right now 49 | if (typeVariables.isNotEmpty()) return 50 | 51 | val functionReturnType = kmClass.getDeclaredTypeNameWithGenerics( 52 | kotlinFrameworkName, 53 | featureContext.kLibClasses 54 | ) 55 | 56 | val constructorParameters = kmClass.getPrimaryConstructor().valueParameters 57 | 58 | val functionParameters = constructorParameters.mapNotNull { parameter -> 59 | 60 | val parameterType = parameter.type ?: return@mapNotNull null 61 | 62 | ParameterSpec.builder( 63 | parameterName = parameter.name, 64 | type = FunctionTypeName.get( 65 | parameters = emptyList(), 66 | returnType = parameterType.toTypeName( 67 | moduleName = kotlinFrameworkName, 68 | isUsedInGenerics = parameterType.shouldUseKotlinTypeWhenHandlingOptionalType() 69 | ).run { 70 | if (Flag.Type.IS_NULLABLE(parameterType.flags)) { 71 | makeOptional() 72 | } else { 73 | this 74 | } 75 | } 76 | ).makeOptional(), 77 | ).defaultValue( 78 | codeBlock = CodeBlock.builder().add("nil").build() 79 | ).build() 80 | } 81 | val functionBody = constructorParameters.joinToString(separator = ",") { property -> 82 | property.name.run { 83 | "$this: ($this != nil) ? $this!() : self.$this" 84 | } 85 | } 86 | 87 | val copyFunction = FunctionSpec.builder("copy") 88 | .addParameters(functionParameters) 89 | .returns(functionReturnType) 90 | .addCode( 91 | CodeBlock.builder() 92 | .addStatement("return %T($functionBody)", functionReturnType) 93 | .build() 94 | ) 95 | .build() 96 | 97 | val extensionSpec = 98 | ExtensionSpec.builder(TypeSpec.classBuilder(functionReturnType.name).build()) 99 | .addFunction(copyFunction) 100 | .addDoc("selector: ${featureContext.prefixedUniqueId}") 101 | .build() 102 | 103 | processorContext.fileSpecBuilder.addExtension(extensionSpec) 104 | } 105 | 106 | class Config : BaseConfig { 107 | override var filter: Filter = Filter.Exclude(emptySet()) 108 | } 109 | 110 | companion object : Factory { 111 | override fun create(block: Config.() -> Unit): DataClassCopyFeature { 112 | val config = Config().apply(block) 113 | return DataClassCopyFeature(featureContext, config.filter) 114 | } 115 | 116 | override val featureContext: KClass = ClassContext::class 117 | 118 | @JvmStatic 119 | override val factory = Companion 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/Filter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.KSwiftRuntimeAnnotations 8 | import dev.icerock.moko.kswift.plugin.context.FeatureContext 9 | import dev.icerock.moko.kswift.plugin.findByClassName 10 | 11 | sealed interface Filter { 12 | fun isShouldProcess(featureContext: CTX): Boolean 13 | 14 | data class Exclude(val names: Set) : Filter { 15 | override fun isShouldProcess(featureContext: CTX): Boolean { 16 | val isExcludedByAnnotation: Boolean = featureContext.annotations 17 | .findByClassName(KSwiftRuntimeAnnotations.KSWIFT_EXCLUDE) != null 18 | val isExcludedByNames: Boolean = names.contains(featureContext.prefixedUniqueId) 19 | 20 | return !isExcludedByAnnotation && !isExcludedByNames 21 | } 22 | } 23 | 24 | data class Include(val names: Set) : Filter { 25 | override fun isShouldProcess(featureContext: CTX): Boolean { 26 | val isIncludedByAnnotation: Boolean = featureContext.annotations 27 | .findByClassName(KSwiftRuntimeAnnotations.KSWIFT_INCLUDE) != null 28 | val isIncludedByNames: Boolean = names.contains(featureContext.prefixedUniqueId) 29 | 30 | return isIncludedByAnnotation || isIncludedByNames 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/PlatformExtensionFunctionsFeature.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.KSwiftRuntimeAnnotations 8 | import dev.icerock.moko.kswift.plugin.context.PackageFunctionContext 9 | import dev.icerock.moko.kswift.plugin.context.classes 10 | import dev.icerock.moko.kswift.plugin.findByClassName 11 | import dev.icerock.moko.kswift.plugin.getStringArgument 12 | import dev.icerock.moko.kswift.plugin.objcNameToSwift 13 | import dev.icerock.moko.kswift.plugin.toTypeName 14 | import io.outfoxx.swiftpoet.AttributeSpec 15 | import io.outfoxx.swiftpoet.AttributeSpec.Companion.ESCAPING 16 | import io.outfoxx.swiftpoet.DeclaredTypeName 17 | import io.outfoxx.swiftpoet.ExtensionSpec 18 | import io.outfoxx.swiftpoet.FileSpec 19 | import io.outfoxx.swiftpoet.FunctionSpec 20 | import io.outfoxx.swiftpoet.FunctionTypeName 21 | import io.outfoxx.swiftpoet.Modifier 22 | import io.outfoxx.swiftpoet.ParameterSpec 23 | import io.outfoxx.swiftpoet.ParameterizedTypeName 24 | import io.outfoxx.swiftpoet.TypeName 25 | import io.outfoxx.swiftpoet.TypeVariableName 26 | import io.outfoxx.swiftpoet.TypeVariableName.Bound.Constraint 27 | import kotlinx.metadata.Flag 28 | import kotlinx.metadata.KmClass 29 | import kotlinx.metadata.KmClassifier 30 | import kotlinx.metadata.KmFunction 31 | import kotlinx.metadata.KmType 32 | import kotlinx.metadata.KmValueParameter 33 | import kotlinx.metadata.KmVariance 34 | import kotlinx.metadata.klib.annotations 35 | import kotlinx.metadata.klib.file 36 | import kotlin.reflect.KClass 37 | 38 | class PlatformExtensionFunctionsFeature( 39 | override val featureContext: KClass, 40 | override val filter: Filter 41 | ) : ProcessorFeature() { 42 | @Suppress("ReturnCount") 43 | override fun doProcess( 44 | featureContext: PackageFunctionContext, 45 | processorContext: ProcessorContext 46 | ) { 47 | val kotlinFrameworkName: String = processorContext.framework.baseName 48 | 49 | val functionData: Data = Processing.read( 50 | context = featureContext, 51 | moduleName = kotlinFrameworkName 52 | ) ?: return 53 | 54 | val extensionSpec: ExtensionSpec = Processing.buildExtensionSpec( 55 | functionData = functionData, 56 | doc = "selector: ${featureContext.prefixedUniqueId}" 57 | ) 58 | 59 | val fileSpecBuilder: FileSpec.Builder = processorContext.fileSpecBuilder 60 | 61 | fileSpecBuilder.addImport(functionData.classTypeName.moduleName) 62 | fileSpecBuilder.addImport(kotlinFrameworkName) 63 | fileSpecBuilder.addExtension(extensionSpec) 64 | } 65 | 66 | class Config : BaseConfig { 67 | override var filter: Filter = Filter.Exclude(emptySet()) 68 | } 69 | 70 | internal sealed interface PlatformClassTypeName { 71 | val typeName: DeclaredTypeName 72 | 73 | data class Normal(override val typeName: DeclaredTypeName) : PlatformClassTypeName 74 | 75 | data class Companion(override val typeName: DeclaredTypeName) : PlatformClassTypeName 76 | } 77 | 78 | internal data class Data( 79 | val classTypeName: DeclaredTypeName, 80 | val funcName: String, 81 | val typeVariables: List, 82 | val funcParams: List, 83 | val modifiers: List, 84 | val returnType: TypeName, 85 | val code: String 86 | ) 87 | 88 | companion object : Factory { 89 | override fun create(block: Config.() -> Unit): PlatformExtensionFunctionsFeature { 90 | val config = Config().apply(block) 91 | return PlatformExtensionFunctionsFeature(featureContext, config.filter) 92 | } 93 | 94 | override val featureContext: KClass = PackageFunctionContext::class 95 | 96 | @JvmStatic 97 | override val factory = Companion 98 | } 99 | 100 | internal object Processing { 101 | 102 | fun buildExtensionSpec(functionData: Data, doc: String): ExtensionSpec { 103 | return ExtensionSpec.builder(functionData.classTypeName.objcNameToSwift()) 104 | .addFunction( 105 | FunctionSpec.builder(functionData.funcName) 106 | .addDoc(doc + "\n") 107 | .addAttribute(AttributeSpec.DISCARDABLE_RESULT) 108 | .addModifiers(Modifier.PUBLIC) 109 | .addTypeVariables(functionData.typeVariables) 110 | .addModifiers(functionData.modifiers) 111 | .addParameters(functionData.funcParams) 112 | .returns(functionData.returnType) 113 | .addCode(functionData.code) 114 | .build() 115 | ) 116 | .addModifiers(Modifier.PUBLIC) 117 | .build() 118 | } 119 | 120 | @Suppress("ReturnCount") 121 | fun read(context: PackageFunctionContext, moduleName: String): Data? { 122 | val func: KmFunction = context.func 123 | if (Flag.IS_PUBLIC(func.flags).not()) return null 124 | 125 | val receiver: KmType = func.receiverParameterType ?: return null 126 | 127 | val fileName: String = func.file?.name ?: return null 128 | val swiftedClass: String = fileName.replace(".kt", "Kt") 129 | 130 | val typeVariables: Map = buildTypeVariables(context, moduleName) 131 | 132 | val classTypeName: PlatformClassTypeName = buildClassTypeName( 133 | type = receiver, 134 | context = context, 135 | moduleName = moduleName, 136 | typeVariables = typeVariables 137 | ) ?: return null 138 | 139 | val funcName: String = func.name 140 | 141 | val funcParams: List = buildFunctionParameters( 142 | featureContext = context, 143 | kotlinFrameworkName = moduleName, 144 | typeVariables = typeVariables 145 | ) 146 | 147 | val modifiers: List = if (classTypeName is PlatformClassTypeName.Companion) { 148 | listOf(Modifier.CLASS) 149 | } else emptyList() 150 | 151 | val callParams: List = func.valueParameters.map { param -> 152 | buildString { 153 | append(buildFunctionParameterName(param)) 154 | append(": ") 155 | append(param.name) 156 | 157 | val type: KmType? = param.type 158 | if (type?.arguments?.any { it.type?.classifier is KmClassifier.TypeParameter } == true) { 159 | append(" as! ") 160 | val fullTypeWithoutTypeParameter: TypeName = type.toTypeName( 161 | moduleName = moduleName, 162 | isUsedInGenerics = false, 163 | typeVariables = typeVariables, 164 | removeTypeVariables = true 165 | ) 166 | append(fullTypeWithoutTypeParameter.toString()) 167 | } 168 | } 169 | } 170 | val callParamsLine: String = listOf("self").plus(callParams).joinToString(", ") 171 | 172 | return Data( 173 | classTypeName = classTypeName.typeName, 174 | funcName = funcName, 175 | typeVariables = typeVariables.values.toList(), 176 | funcParams = funcParams, 177 | modifiers = modifiers, 178 | returnType = func.returnType.toTypeName(moduleName, typeVariables = typeVariables), 179 | code = "return $swiftedClass.$funcName($callParamsLine)\n" 180 | ) 181 | } 182 | 183 | private fun buildTypeVariables( 184 | context: PackageFunctionContext, 185 | moduleName: String 186 | ): Map { 187 | val func: KmFunction = context.func 188 | 189 | val resultMap: MutableMap = mutableMapOf() 190 | 191 | func.typeParameters.forEach { typeParam -> 192 | when (typeParam.variance) { 193 | KmVariance.INVARIANT -> TypeVariableName.typeVariable( 194 | typeParam.name, 195 | typeParam.upperBounds.map { type -> 196 | TypeVariableName.bound( 197 | Constraint.CONFORMS_TO, 198 | type.toTypeName( 199 | moduleName = moduleName, 200 | isUsedInGenerics = true, 201 | typeVariables = resultMap 202 | ) 203 | ) 204 | } 205 | ).also { resultMap[typeParam.id] = it } 206 | KmVariance.IN -> TODO() 207 | KmVariance.OUT -> TODO() 208 | } 209 | } 210 | 211 | return resultMap 212 | } 213 | 214 | @Suppress("ReturnCount") 215 | private fun buildClassTypeName( 216 | type: KmType, 217 | context: PackageFunctionContext, 218 | moduleName: String, 219 | typeVariables: Map 220 | ): PlatformClassTypeName? { 221 | val isCompanion: Boolean = (type.classifier as? KmClassifier.Class) 222 | ?.name?.endsWith(".Companion") == true 223 | 224 | val typeName: TypeName = type.toTypeName( 225 | moduleName = moduleName, 226 | typeVariables = typeVariables, 227 | removeTypeVariables = true 228 | ) 229 | 230 | val declaredTypeName: DeclaredTypeName = typeName as? DeclaredTypeName ?: return null 231 | 232 | if (declaredTypeName.moduleName != moduleName) { 233 | return if (isCompanion) PlatformClassTypeName.Companion(declaredTypeName) 234 | else PlatformClassTypeName.Normal(declaredTypeName) 235 | } 236 | 237 | // extensions for kotlin classes will be available out of box 238 | if (isCompanion) return null 239 | 240 | // generate extensions only for interfaces 241 | val className = (type.classifier as? KmClassifier.Class)?.name 242 | ?: return PlatformClassTypeName.Normal(declaredTypeName) 243 | 244 | val clazz: KmClass = context.classes.firstOrNull { it.name == className } 245 | ?: return PlatformClassTypeName.Normal(declaredTypeName) 246 | 247 | val isInterface: Boolean = Flag.Class.IS_INTERFACE(clazz.flags) 248 | return if (!isInterface) null 249 | else PlatformClassTypeName.Normal(declaredTypeName) 250 | } 251 | 252 | private fun buildFunctionParameters( 253 | featureContext: PackageFunctionContext, 254 | kotlinFrameworkName: String, 255 | typeVariables: Map 256 | ): List { 257 | val func: KmFunction = featureContext.func 258 | val classes: List = featureContext.classes 259 | return func.valueParameters.map { param -> 260 | val paramType: KmType = param.type 261 | ?: throw IllegalArgumentException("extension ${func.name} have null type for $param") 262 | val type: TypeName = paramType.toTypeName( 263 | kotlinFrameworkName, 264 | typeVariables = typeVariables 265 | ) 266 | 267 | val usedType: TypeName = when (type) { 268 | is ParameterizedTypeName -> { 269 | val paramTypeClassifier = paramType.classifier 270 | if (paramTypeClassifier !is KmClassifier.Class) { 271 | throw IllegalArgumentException("extension ${func.name} have not class param $param") 272 | } 273 | 274 | val paramClassName = paramTypeClassifier.name 275 | 276 | val isWithoutGenerics = 277 | classes.firstOrNull { it.name == paramClassName }?.let { 278 | Flag.Class.IS_INTERFACE(it.flags) 279 | } ?: false 280 | 281 | if (isWithoutGenerics) type.rawType 282 | else type 283 | } 284 | else -> type 285 | } 286 | 287 | ParameterSpec.builder(parameterName = param.name, type = usedType) 288 | .apply { if (type is FunctionTypeName) addAttribute(ESCAPING) } 289 | .build() 290 | } 291 | } 292 | 293 | private fun buildFunctionParameterName(param: KmValueParameter): String { 294 | return param.annotations 295 | .findByClassName(KSwiftRuntimeAnnotations.KSWIFT_OVERRIDE_NAME) 296 | ?.getStringArgument("newParamName") ?: param.name 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/ProcessorFeature.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.context.FeatureContext 8 | import io.outfoxx.swiftpoet.FileSpec 9 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 10 | import kotlin.reflect.KClass 11 | 12 | abstract class ProcessorFeature { 13 | abstract val featureContext: KClass 14 | abstract val filter: Filter 15 | 16 | fun process(featureContext: CTX, processorContext: ProcessorContext) { 17 | if (filter.isShouldProcess(featureContext).not()) return 18 | 19 | doProcess(featureContext, processorContext) 20 | } 21 | 22 | protected abstract fun doProcess(featureContext: CTX, processorContext: ProcessorContext) 23 | 24 | interface Factory, Config : BaseConfig> { 25 | fun create(block: Config.() -> Unit): F 26 | 27 | val featureContext: KClass 28 | 29 | val factory: Factory 30 | } 31 | } 32 | 33 | data class ProcessorContext( 34 | val fileSpecBuilder: FileSpec.Builder, 35 | val framework: Framework 36 | ) 37 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/main/kotlin/dev/icerock/moko/kswift/plugin/feature/SealedToSwiftEnumFeature.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin.feature 6 | 7 | import dev.icerock.moko.kswift.plugin.buildTypeVariableNames 8 | import dev.icerock.moko.kswift.plugin.context.ClassContext 9 | import dev.icerock.moko.kswift.plugin.context.kLibClasses 10 | import dev.icerock.moko.kswift.plugin.getDeclaredTypeNameWithGenerics 11 | import dev.icerock.moko.kswift.plugin.getSimpleName 12 | import io.outfoxx.swiftpoet.CodeBlock 13 | import io.outfoxx.swiftpoet.EnumerationCaseSpec 14 | import io.outfoxx.swiftpoet.FunctionSpec 15 | import io.outfoxx.swiftpoet.Modifier 16 | import io.outfoxx.swiftpoet.ParameterizedTypeName 17 | import io.outfoxx.swiftpoet.PropertySpec 18 | import io.outfoxx.swiftpoet.TypeName 19 | import io.outfoxx.swiftpoet.TypeSpec 20 | import io.outfoxx.swiftpoet.TypeVariableName 21 | import kotlinx.metadata.ClassName 22 | import kotlinx.metadata.Flag 23 | import kotlinx.metadata.KmClass 24 | import java.util.Locale 25 | import kotlin.reflect.KClass 26 | 27 | class SealedToSwiftEnumFeature( 28 | override val featureContext: KClass, 29 | override val filter: Filter 30 | ) : ProcessorFeature() { 31 | 32 | @Suppress("ReturnCount") 33 | override fun doProcess(featureContext: ClassContext, processorContext: ProcessorContext) { 34 | if (featureContext.clazz.sealedSubclasses.isEmpty()) return 35 | 36 | val kotlinFrameworkName: String = processorContext.framework.baseName 37 | val kmClass: KmClass = featureContext.clazz 38 | 39 | if (Flag.IS_PUBLIC(kmClass.flags).not()) return 40 | 41 | val sealedCases: List = buildEnumCases(kotlinFrameworkName, featureContext) 42 | if (sealedCases.isEmpty()) return 43 | 44 | val typeVariables: List = 45 | kmClass.buildTypeVariableNames(kotlinFrameworkName) 46 | 47 | val originalClassName: String = getSimpleName(kmClass.name, featureContext.kLibClasses) 48 | val className: String = originalClassName.replace(".", "").plus("Ks") 49 | val enumType: TypeSpec = TypeSpec.enumBuilder(className) 50 | .addDoc("selector: ${featureContext.prefixedUniqueId}") 51 | .apply { 52 | typeVariables.forEach { addTypeVariable(it) } 53 | sealedCases.forEach { addEnumCase(it.enumCaseSpec) } 54 | } 55 | .addModifiers(Modifier.PUBLIC) 56 | .addFunction( 57 | buildEnumConstructor( 58 | featureContext = featureContext, 59 | kotlinFrameworkName = kotlinFrameworkName, 60 | sealedCases = sealedCases, 61 | className = className, 62 | originalClassName = originalClassName 63 | ) 64 | ) 65 | .addProperty( 66 | buildSealedProperty( 67 | featureContext = featureContext, 68 | kotlinFrameworkName = kotlinFrameworkName, 69 | sealedCases = sealedCases 70 | ) 71 | ) 72 | .build() 73 | 74 | processorContext.fileSpecBuilder.addType(enumType) 75 | } 76 | 77 | private fun buildEnumConstructor( 78 | featureContext: ClassContext, 79 | kotlinFrameworkName: String, 80 | sealedCases: List, 81 | className: String, 82 | originalClassName: String 83 | ): FunctionSpec { 84 | return FunctionSpec.builder("init") 85 | .addModifiers(Modifier.PUBLIC) 86 | .addParameter( 87 | label = "_", 88 | name = "obj", 89 | type = featureContext.clazz.getDeclaredTypeNameWithGenerics( 90 | kotlinFrameworkName = kotlinFrameworkName, 91 | classes = featureContext.kLibClasses 92 | ) 93 | ) 94 | .addCode( 95 | CodeBlock.builder() 96 | .apply { 97 | sealedCases.forEachIndexed { index, enumCase -> 98 | buildString { 99 | if (index != 0) append("} else ") 100 | append("if ") 101 | append(enumCase.initCheck) 102 | append(" {") 103 | append('\n') 104 | }.also { add(it) } 105 | indent() 106 | buildString { 107 | append("self = .") 108 | append(enumCase.name) 109 | append(enumCase.initBlock) 110 | append('\n') 111 | }.also { add(it) } 112 | unindent() 113 | } 114 | add("} else {\n") 115 | indent() 116 | add("fatalError(\"$className not synchronized with $originalClassName class\")\n") 117 | unindent() 118 | add("}\n") 119 | } 120 | .build() 121 | ) 122 | .build() 123 | } 124 | 125 | private fun buildEnumCases( 126 | kotlinFrameworkName: String, 127 | featureContext: ClassContext 128 | ): List { 129 | val kmClass = featureContext.clazz 130 | return kmClass.sealedSubclasses.mapNotNull { sealedClassName -> 131 | val sealedClass: KmClass = featureContext.parentContext 132 | .fragment.classes.first { it.name == sealedClassName } 133 | 134 | if (Flag.IS_PUBLIC(sealedClass.flags).not()) return@mapNotNull null 135 | 136 | buildEnumCase(kotlinFrameworkName, featureContext, sealedClassName, sealedClass) 137 | } 138 | } 139 | 140 | private fun buildEnumCase( 141 | kotlinFrameworkName: String, 142 | featureContext: ClassContext, 143 | subclassName: ClassName, 144 | sealedCaseClass: KmClass 145 | ): EnumCase { 146 | val kmClass = featureContext.clazz 147 | val name: String = if (subclassName.startsWith(kmClass.name)) { 148 | subclassName.removePrefix(kmClass.name).removePrefix(".") 149 | } else subclassName.removePrefix(kmClass.name.substringBeforeLast("/")).removePrefix("/") 150 | val decapitalizedName: String = name.decapitalize(Locale.ROOT) 151 | 152 | val isObject: Boolean = Flag.Class.IS_OBJECT(sealedCaseClass.flags) 153 | val caseArg = sealedCaseClass.getDeclaredTypeNameWithGenerics( 154 | kotlinFrameworkName = kotlinFrameworkName, 155 | classes = featureContext.kLibClasses 156 | ) 157 | 158 | return EnumCase( 159 | name = decapitalizedName, 160 | param = if (isObject) null else caseArg, 161 | initCheck = if (isObject) { 162 | "obj is $caseArg" 163 | } else { 164 | "let obj = obj as? $caseArg" 165 | }, 166 | initBlock = if (isObject) "" else "(obj)", 167 | caseArg = caseArg, 168 | caseBlock = if (isObject) "" else "(let obj)" 169 | ) 170 | } 171 | 172 | private fun buildSealedProperty( 173 | featureContext: ClassContext, 174 | kotlinFrameworkName: String, 175 | sealedCases: List 176 | ): PropertySpec { 177 | val returnType: TypeName = featureContext.clazz.getDeclaredTypeNameWithGenerics( 178 | kotlinFrameworkName = kotlinFrameworkName, 179 | classes = featureContext.kLibClasses 180 | ) 181 | return PropertySpec.builder("sealed", type = returnType) 182 | .addModifiers(Modifier.PUBLIC) 183 | .getter( 184 | FunctionSpec 185 | .getterBuilder() 186 | .addCode(buildSealedPropertyBody(sealedCases, returnType)) 187 | .build() 188 | ).build() 189 | } 190 | 191 | private fun buildSealedPropertyBody( 192 | sealedCases: List, 193 | returnType: TypeName 194 | ): CodeBlock = CodeBlock.builder().apply { 195 | add("switch self {\n") 196 | sealedCases.forEach { enumCase -> 197 | buildString { 198 | append("case .") 199 | append(enumCase.name) 200 | append(enumCase.caseBlock) 201 | append(":\n") 202 | }.also { add(it) } 203 | indent() 204 | addSealedCaseReturnCode(enumCase, returnType) 205 | unindent() 206 | } 207 | add("}\n") 208 | }.build() 209 | 210 | private fun CodeBlock.Builder.addSealedCaseReturnCode( 211 | enumCase: EnumCase, 212 | returnType: TypeName 213 | ) { 214 | val paramType: TypeName? = enumCase.param 215 | val cast: String 216 | val returnedName: String 217 | 218 | if (paramType == null) { 219 | returnedName = "${enumCase.caseArg}()" 220 | cast = if (returnType is ParameterizedTypeName) { 221 | // The return type is generic and there is no parameter, so it can 222 | // be assumed that the case is NOT generic. Thus the case needs to 223 | // be force-cast. 224 | "as!" 225 | } else { 226 | // The return type is NOT generic and there is no parameter, so a 227 | // regular cast can be used. 228 | "as" 229 | } 230 | } else { 231 | // There is a parameter 232 | returnedName = "obj" 233 | cast = if (paramType is ParameterizedTypeName && returnType is ParameterizedTypeName) { 234 | if (paramType.typeArguments == returnType.typeArguments) { 235 | // The parameter and return type have the same generic pattern. This 236 | // is true if both are NOT generic OR if both are generic. Thus a 237 | // regular cast can be used. 238 | "as" 239 | } else { 240 | "as!" 241 | } 242 | } else if (paramType !is ParameterizedTypeName && returnType !is ParameterizedTypeName) { 243 | // If the parameter and return type is not ParameterizedTypeName 244 | // regular cast can be used. 245 | "as" 246 | } else { 247 | // If the parameter and return type have differing generic patterns 248 | // then a force-cast is needed. 249 | "as!" 250 | } 251 | } 252 | add("return $returnedName $cast $returnType\n") 253 | } 254 | 255 | data class EnumCase( 256 | val name: String, 257 | val param: TypeName?, 258 | val initCheck: String, 259 | val initBlock: String, 260 | val caseArg: TypeName, 261 | val caseBlock: String 262 | ) { 263 | val enumCaseSpec: EnumerationCaseSpec 264 | get() { 265 | return if (param == null) { 266 | EnumerationCaseSpec.builder(name) 267 | } else { 268 | EnumerationCaseSpec.builder(name, param) 269 | }.build() 270 | } 271 | } 272 | 273 | class Config : BaseConfig { 274 | override var filter: Filter = Filter.Exclude(emptySet()) 275 | } 276 | 277 | companion object : Factory { 278 | override fun create(block: Config.() -> Unit): SealedToSwiftEnumFeature { 279 | val config = Config().apply(block) 280 | return SealedToSwiftEnumFeature(featureContext, config.filter) 281 | } 282 | 283 | override val featureContext: KClass = ClassContext::class 284 | 285 | @JvmStatic 286 | override val factory = Companion 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/test/kotlin/dev/icerock/moko/kswift/plugin/KmClassExtKtTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import kotlinx.metadata.KmClass 8 | import kotlin.test.BeforeTest 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | 12 | class KmClassExtKtTest { 13 | private lateinit var classes: List 14 | 15 | @BeforeTest 16 | fun setup() { 17 | val klib = readKLib("mpp-library.klib") 18 | classes = klib.fragments.flatMap { it.classes } 19 | } 20 | 21 | @Test 22 | fun `simple class name`() { 23 | val output = getSimpleName( 24 | className = "com/icerockdev/library/TestViewModel", 25 | classes = classes 26 | ) 27 | assertEquals(expected = "TestViewModel", actual = output) 28 | } 29 | 30 | @Test 31 | fun `nested class name`() { 32 | val output = getSimpleName( 33 | className = "com/icerockdev/library/TestViewModel.ScreenStateClass", 34 | classes = classes 35 | ) 36 | assertEquals(expected = "TestViewModel.ScreenStateClass", actual = output) 37 | } 38 | 39 | @Test 40 | fun `nested sealed class name`() { 41 | val output = getSimpleName( 42 | className = "com/icerockdev/library/TestViewModel.ScreenStateClass.Authorized", 43 | classes = classes 44 | ) 45 | assertEquals(expected = "TestViewModel.ScreenStateClassAuthorized", actual = output) 46 | } 47 | 48 | @Test 49 | fun `nested interface name`() { 50 | val output = getSimpleName( 51 | className = "com/icerockdev/library/TestViewModel.ScreenState", 52 | classes = classes 53 | ) 54 | assertEquals(expected = "TestViewModelScreenState", actual = output) 55 | } 56 | 57 | @Test 58 | fun `nested interface sealed name`() { 59 | val output = getSimpleName( 60 | className = "com/icerockdev/library/TestViewModel.ScreenState.Authorized", 61 | classes = classes 62 | ) 63 | assertEquals(expected = "TestViewModelScreenStateAuthorized", actual = output) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/test/kotlin/dev/icerock/moko/kswift/plugin/PlatformExtensionsTest.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MaxLineLength") 2 | 3 | /* 4 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 5 | */ 6 | 7 | package dev.icerock.moko.kswift.plugin 8 | 9 | import dev.icerock.moko.kswift.plugin.context.LibraryContext 10 | import dev.icerock.moko.kswift.plugin.context.PackageFunctionContext 11 | import dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature 12 | import io.outfoxx.swiftpoet.ExtensionSpec 13 | import kotlinx.metadata.KmClassifier 14 | import kotlinx.metadata.klib.KlibModuleMetadata 15 | import kotlin.test.Test 16 | import kotlin.test.assertEquals 17 | import kotlin.test.assertNotNull 18 | 19 | class PlatformExtensionsTest { 20 | @Test 21 | fun testBindExtensions() { 22 | val metadata: KlibModuleMetadata = readKLib("mpp-library.klib") 23 | val libraryContext = LibraryContext(metadata) 24 | 25 | assertNotNull(libraryContext) 26 | 27 | var checkedCount = 0 28 | 29 | libraryContext.visit { featureContext -> 30 | if (featureContext is PackageFunctionContext) { 31 | if (featureContext.func.name == "bindGenericText") { 32 | testUILabelBindGenericText(featureContext) 33 | checkedCount++ 34 | } 35 | 36 | if (featureContext.func.name == "bindGenericAny") { 37 | testUILabelBindGenericAny(featureContext) 38 | checkedCount++ 39 | } 40 | } 41 | } 42 | 43 | assertEquals(expected = 2, actual = checkedCount) 44 | } 45 | 46 | private fun testUILabelBindGenericText(featureContext: PackageFunctionContext) { 47 | val data: PlatformExtensionFunctionsFeature.Data? = 48 | PlatformExtensionFunctionsFeature.Processing.read( 49 | context = featureContext, 50 | moduleName = "shared" 51 | ) 52 | 53 | assertNotNull(data) 54 | 55 | val extensionSpec: ExtensionSpec = 56 | PlatformExtensionFunctionsFeature.Processing.buildExtensionSpec( 57 | functionData = data, 58 | doc = "test doc" 59 | ) 60 | 61 | assertEquals( 62 | expected = 63 | """ 64 | public extension UIKit.UILabel { 65 | 66 | /** 67 | * test doc 68 | */ 69 | @discardableResult 70 | public func bindGenericText(liveData: shared.LiveData) -> shared.Closeable { 71 | return UILabelExtKt.bindGenericText(self, liveData: liveData as! shared.LiveData) 72 | } 73 | 74 | }""" 75 | .trimIndent(), 76 | actual = extensionSpec.toString().trim() 77 | ) 78 | } 79 | 80 | private fun testUILabelBindGenericAny(featureContext: PackageFunctionContext) { 81 | val data: PlatformExtensionFunctionsFeature.Data? = 82 | PlatformExtensionFunctionsFeature.Processing.read( 83 | context = featureContext, 84 | moduleName = "shared" 85 | ) 86 | 87 | assertNotNull(data) 88 | 89 | val extensionSpec: ExtensionSpec = 90 | PlatformExtensionFunctionsFeature.Processing.buildExtensionSpec( 91 | functionData = data, 92 | doc = "test doc" 93 | ) 94 | 95 | assertEquals( 96 | expected = 97 | """ 98 | public extension UIKit.UILabel { 99 | 100 | /** 101 | * test doc 102 | */ 103 | @discardableResult 104 | public func bindGenericAny(liveData: shared.LiveData) -> shared.Closeable { 105 | return UILabelExtKt.bindGenericAny(self, liveData: liveData as! shared.LiveData) 106 | } 107 | 108 | }""" 109 | .trimIndent(), 110 | actual = extensionSpec.toString().trim() 111 | ) 112 | } 113 | 114 | @Test 115 | fun testSetHandlerExtensions() { 116 | val metadata: KlibModuleMetadata = readKLib("mvvm-livedata.klib") 117 | val libraryContext = LibraryContext(metadata) 118 | 119 | assertNotNull(libraryContext) 120 | 121 | var checkedCount = 0 122 | 123 | libraryContext.visit { featureContext -> 124 | if (featureContext is PackageFunctionContext) { 125 | if ( 126 | featureContext.func.receiverParameterType?.classifier is KmClassifier.TypeParameter && 127 | featureContext.func.name == "setEventHandler" 128 | ) { 129 | testUIControlSetEventHandler(featureContext) 130 | checkedCount++ 131 | } else if (featureContext.func.name == "setEventHandler") { 132 | testNotificationCenterSetEventHandler(featureContext) 133 | checkedCount++ 134 | } 135 | } 136 | } 137 | 138 | assertEquals(expected = 2, actual = checkedCount) 139 | } 140 | 141 | private fun testUIControlSetEventHandler(featureContext: PackageFunctionContext) { 142 | val data: PlatformExtensionFunctionsFeature.Data? = 143 | PlatformExtensionFunctionsFeature.Processing.read( 144 | context = featureContext, 145 | moduleName = "shared" 146 | ) 147 | 148 | assertNotNull(data) 149 | 150 | val extensionSpec: ExtensionSpec = 151 | PlatformExtensionFunctionsFeature.Processing.buildExtensionSpec( 152 | functionData = data, 153 | doc = "test doc" 154 | ) 155 | 156 | assertEquals( 157 | expected = 158 | """ 159 | public extension UIKit.UIControl { 160 | 161 | /** 162 | * test doc 163 | */ 164 | @discardableResult 165 | public func setEventHandler(event: Swift.UInt64, lambda: @escaping (T) -> Swift.Void) -> shared.Closeable { 166 | return UIControlExtKt.setEventHandler(self, event: event, lambda: lambda as! (UIKit.UIControl) -> Swift.Void) 167 | } 168 | 169 | }""" 170 | .trimIndent(), 171 | actual = extensionSpec.toString().trim() 172 | ) 173 | } 174 | 175 | private fun testNotificationCenterSetEventHandler(featureContext: PackageFunctionContext) { 176 | val data: PlatformExtensionFunctionsFeature.Data? = 177 | PlatformExtensionFunctionsFeature.Processing.read( 178 | context = featureContext, 179 | moduleName = "shared" 180 | ) 181 | 182 | assertNotNull(data) 183 | 184 | val extensionSpec: ExtensionSpec = 185 | PlatformExtensionFunctionsFeature.Processing.buildExtensionSpec( 186 | functionData = data, 187 | doc = "test doc" 188 | ) 189 | 190 | assertEquals( 191 | expected = 192 | """ 193 | public extension Foundation.NotificationCenter { 194 | 195 | /** 196 | * test doc 197 | */ 198 | @discardableResult 199 | public func setEventHandler( 200 | notification: Swift.String, 201 | ref: T, 202 | lambda: @escaping (T) -> Swift.Void 203 | ) -> shared.Closeable { 204 | return NSNotificationCenterExtKt.setEventHandler(self, notification: notification, ref: ref, lambda: lambda as! (Swift.AnyObject) -> Swift.Void) 205 | } 206 | 207 | }""" 208 | .trimIndent(), 209 | actual = extensionSpec.toString().trim() 210 | ) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/test/kotlin/dev/icerock/moko/kswift/plugin/klibUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift.plugin 6 | 7 | import kotlinx.metadata.klib.KlibModuleMetadata 8 | import java.io.File 9 | import java.io.InputStream 10 | 11 | fun readKLib(name: String): KlibModuleMetadata { 12 | val classLoader = KmClassExtKtTest::class.java.classLoader 13 | val inputStream: InputStream = classLoader.getResourceAsStream(name) 14 | val tempFile: File = File.createTempFile("kswift-test", ".klib") 15 | tempFile.outputStream().use { output -> 16 | inputStream.use { input -> 17 | input.copyTo(output) 18 | } 19 | } 20 | return KotlinMetadataLibraryProvider.readLibraryMetadata(tempFile).also { 21 | tempFile.delete() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/test/resources/mpp-library.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-kswift/cccacec4041b52084c6f1200ce25e99ebc55818d/kswift-gradle-plugin/src/test/resources/mpp-library.klib -------------------------------------------------------------------------------- /kswift-gradle-plugin/src/test/resources/mvvm-livedata.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-kswift/cccacec4041b52084c6f1200ce25e99ebc55818d/kswift-gradle-plugin/src/test/resources/mvvm-livedata.klib -------------------------------------------------------------------------------- /kswift-runtime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.all") 7 | id("dev.icerock.moko.gradle.publication") 8 | id("dev.icerock.moko.gradle.publication.hosts") 9 | id("dev.icerock.moko.gradle.stub.javadoc") 10 | id("dev.icerock.moko.gradle.detekt") 11 | } 12 | -------------------------------------------------------------------------------- /kswift-runtime/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /kswift-runtime/src/commonMain/kotlin/dev/icerock/moko/kswift/Annotations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.kswift 6 | 7 | @Retention(AnnotationRetention.BINARY) 8 | annotation class KSwiftInclude 9 | 10 | @Retention(AnnotationRetention.BINARY) 11 | annotation class KSwiftExclude 12 | 13 | @Retention(AnnotationRetention.BINARY) 14 | @Target(AnnotationTarget.VALUE_PARAMETER) 15 | annotation class KSwiftOverrideName(val newParamName: String) 16 | -------------------------------------------------------------------------------- /sample/android-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.android.application") 7 | } 8 | 9 | android { 10 | defaultConfig { 11 | applicationId = "dev.icerock.moko.samples.kswift" 12 | 13 | versionCode = 1 14 | versionName = "0.1.0" 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation(libs.appCompat) 20 | implementation(libs.material) 21 | 22 | implementation(projects.sample.mppLibrary) 23 | } 24 | -------------------------------------------------------------------------------- /sample/android-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /opt/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/android-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/android-app/src/main/java/com/icerockdev/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.app 6 | 7 | import androidx.appcompat.app.AppCompatActivity 8 | 9 | class MainActivity : androidx.appcompat.app.AppCompatActivity(R.layout.activity_main) 10 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/ios-app/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://cdn.cocoapods.org/' 2 | 3 | # ignore all warnings from all pods 4 | inhibit_all_warnings! 5 | 6 | use_frameworks! 7 | platform :ios, '11.0' 8 | 9 | # workaround for https://github.com/CocoaPods/CocoaPods/issues/8073 10 | # need for correct invalidate of cache MultiPlatformLibrary.framework 11 | install! 'cocoapods', :disable_input_output_paths => true 12 | 13 | target 'ios-app' do 14 | # MultiPlatformLibrary 15 | pod 'MultiPlatformLibrary', :path => '../mpp-library' 16 | pod 'MultiPlatformLibrarySwift', :path => '../mpp-library' 17 | 18 | target :Tests 19 | end 20 | 21 | target 'pods-test' do 22 | pod 'mpp_library_pods', :path => '../mpp-library-pods' 23 | pod 'mpp_library_podsSwift', :path => '../mpp-library-pods' 24 | end 25 | -------------------------------------------------------------------------------- /sample/ios-app/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - mpp_library_pods (1.0) 3 | - mpp_library_podsSwift (1.0): 4 | - mpp_library_pods 5 | - MultiPlatformLibrary (0.1.0) 6 | - MultiPlatformLibrarySwift (1.0): 7 | - MultiPlatformLibrary 8 | 9 | DEPENDENCIES: 10 | - mpp_library_pods (from `../mpp-library-pods`) 11 | - mpp_library_podsSwift (from `../mpp-library-pods`) 12 | - MultiPlatformLibrary (from `../mpp-library`) 13 | - MultiPlatformLibrarySwift (from `../mpp-library`) 14 | 15 | EXTERNAL SOURCES: 16 | mpp_library_pods: 17 | :path: "../mpp-library-pods" 18 | mpp_library_podsSwift: 19 | :path: "../mpp-library-pods" 20 | MultiPlatformLibrary: 21 | :path: "../mpp-library" 22 | MultiPlatformLibrarySwift: 23 | :path: "../mpp-library" 24 | 25 | SPEC CHECKSUMS: 26 | mpp_library_pods: 2898aa5582d082ae73a1a7478760dbdc7b9b14aa 27 | mpp_library_podsSwift: 61a3b520b6c9f8f88be78338579d08ccc5c2113c 28 | MultiPlatformLibrary: 77bbe416e0d00cc307076f762785483b29138876 29 | MultiPlatformLibrarySwift: 5fd18f0349a2f6561deb6d8be3f7fe35fec6cd9a 30 | 31 | PODFILE CHECKSUM: a97fc6edc5e9b73e7270e05c66045254f3a80c87 32 | 33 | COCOAPODS: 1.11.2 34 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/AnyObjectSealedClassToEnum.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | class AnyObjectSealedClassToEnum: XCTestCase { 10 | 11 | func testSuccessState() throws { 12 | if case .success(let value) = StatusKs(StatusKt.successStatus) { 13 | XCTAssertEqual(value.data, "hi!") 14 | } else { XCTFail() } 15 | } 16 | 17 | func testFailureState() throws { 18 | if case .failure(let value) = StatusKs(StatusKt.failureStatus) { 19 | XCTAssertTrue(value.exception is KotlinRuntimeException) 20 | } else { XCTFail() } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/DataClassCopyTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | /** 10 | copy method needs to be generated 11 | */ 12 | class DataClassCopyTests: XCTestCase { 13 | 14 | func givenADataClassWhenUsingBetterCopyThenCheckThatProvidedArgumentsChangeRelatedPropertiesAndNotProvidedArgumentsPropertiesStayTheSame() throws { 15 | let dataClass = DataClass( 16 | stringValue: "aValue", 17 | optionalStringValue: nil, 18 | intValue: 0, 19 | optionalIntValue: nil, 20 | booleanValue: false, 21 | optionalBooleanValue: nil 22 | ) 23 | let dataClassWithNewValues = dataClass.copy(stringValue: {"aNewValue"}, intValue: {1}, booleanValue: {true}) 24 | XCTAssertEqual("aValue", dataClass.stringValue) 25 | XCTAssertEqual("aNewValue", dataClassWithNewValues.stringValue) 26 | XCTAssertEqual(dataClass.optionalStringValue, dataClassWithNewValues.optionalStringValue) 27 | XCTAssertEqual(0, dataClass.intValue) 28 | XCTAssertEqual(1, dataClassWithNewValues.intValue) 29 | XCTAssertEqual(dataClass.optionalIntValue, dataClassWithNewValues.optionalIntValue) 30 | XCTAssertFalse(dataClass.booleanValue) 31 | XCTAssertTrue(dataClassWithNewValues.booleanValue) 32 | XCTAssertEqual(dataClass.optionalBooleanValue, dataClassWithNewValues.optionalBooleanValue) 33 | } 34 | 35 | func givenADataClassWhenUsingBetterCopyThenCheckThatProvidedArgumentsChangeRelatedOptionalPropertiesAndNotProvidedArgumentsPropertiesStayTheSame() throws { 36 | let dataClass = DataClass( 37 | stringValue: "aValue", 38 | optionalStringValue: nil, 39 | intValue: 0, 40 | optionalIntValue: nil, 41 | booleanValue: false, 42 | optionalBooleanValue: nil 43 | ) 44 | let dataClassWithNewValues = dataClass.copy(optionalStringValue: {"aNewOptionalValue"}, optionalIntValue: {1}, optionalBooleanValue: {true}) 45 | XCTAssertEqual(dataClass.stringValue, dataClassWithNewValues.stringValue) 46 | XCTAssertEqual(nil, dataClass.optionalStringValue) 47 | XCTAssertEqual("aNewOptionalValue", dataClassWithNewValues.optionalStringValue) 48 | XCTAssertEqual(dataClass.intValue, dataClassWithNewValues.intValue) 49 | XCTAssertEqual(nil, dataClass.optionalIntValue) 50 | XCTAssertEqual(1, dataClassWithNewValues.optionalIntValue) 51 | XCTAssertEqual(dataClass.booleanValue, dataClassWithNewValues.booleanValue) 52 | XCTAssertNil(dataClass.optionalBooleanValue) 53 | XCTAssertTrue(dataClassWithNewValues.optionalBooleanValue!.boolValue) 54 | } 55 | 56 | func givenADataClassWhenUsingBetterCopyToSetAPropertyAsNilThenCheckThatIsPossibleToDoIt() throws { 57 | let dataClass = DataClass( 58 | stringValue: "aValue", 59 | optionalStringValue: "aNonOptionalValue", 60 | intValue: 0, 61 | optionalIntValue: 10, 62 | booleanValue: false, 63 | optionalBooleanValue: true 64 | ) 65 | let dataClassWithNewValues = dataClass.copy(optionalStringValue: {nil}, optionalIntValue: {nil}, optionalBooleanValue: {nil}) 66 | XCTAssertEqual(dataClass.stringValue, dataClassWithNewValues.stringValue) 67 | XCTAssertEqual("aNonOptionalValue", dataClass.optionalStringValue) 68 | XCTAssertNil(dataClassWithNewValues.optionalStringValue) 69 | XCTAssertEqual(dataClass.intValue, dataClassWithNewValues.intValue) 70 | XCTAssertEqual(10, dataClass.optionalIntValue) 71 | XCTAssertNil(dataClassWithNewValues.optionalIntValue) 72 | XCTAssertEqual(dataClass.booleanValue, dataClassWithNewValues.booleanValue) 73 | XCTAssertTrue(dataClass.optionalBooleanValue!.boolValue) 74 | XCTAssertNil(dataClassWithNewValues.optionalBooleanValue?.boolValue) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/ExternalGenericSealedClassesToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension TestExternalGenericSealedClass { 11 | var withoutPropertyKs: ExternalGenericSealedClassKs { 12 | get { 13 | return ExternalGenericSealedClassKs(self.withoutProperty) 14 | } 15 | } 16 | var withOnePropertyTKs: ExternalGenericSealedClassKs { 17 | get { 18 | return ExternalGenericSealedClassKs(self.withOnePropertyT) 19 | } 20 | } 21 | var withOnePropertyUKs: ExternalGenericSealedClassKs { 22 | get { 23 | return ExternalGenericSealedClassKs(self.withOnePropertyU) 24 | } 25 | } 26 | var withTwoPropertiesKs: ExternalGenericSealedClassKs { 27 | get { 28 | return ExternalGenericSealedClassKs(self.withTwoProperties) 29 | } 30 | } 31 | } 32 | // <=== NEED TO GENERATE 33 | 34 | class ExternalGenericSealedClassToSwiftEnumTests: XCTestCase { 35 | private let testSource = TestExternalGenericSealedClass() 36 | 37 | func testWithoutProperty() throws { 38 | if case .externalGenericWithoutProperty = testSource.withoutPropertyKs { } else { XCTFail() } 39 | XCTAssertEqual(testSource.withoutPropertyKs.sealed, ExternalGenericWithoutProperty()) 40 | } 41 | 42 | func testWithOnePropertyT() throws { 43 | if case .externalGenericWithOnePropertyT(let data) = testSource.withOnePropertyTKs { 44 | XCTAssertEqual(data.value, "test") 45 | } else { XCTFail() } 46 | XCTAssertEqual(testSource.withOnePropertyTKs.sealed, ExternalGenericWithOnePropertyT(value: "test")) 47 | } 48 | 49 | func testWithOnePropertyU() throws { 50 | if case .externalGenericWithOnePropertyU(let data) = testSource.withOnePropertyUKs { 51 | XCTAssertEqual(data.value, "test") 52 | } else { XCTFail() } 53 | XCTAssertEqual(testSource.withOnePropertyUKs.sealed, ExternalGenericWithOnePropertyU(value: "test")) 54 | } 55 | 56 | func testWithTwoProperties() throws { 57 | if case .externalGenericWithTwoProperties(let data) = testSource.withTwoPropertiesKs { 58 | XCTAssertEqual(data.value1, "test1") 59 | XCTAssertEqual(data.value2, "test2") 60 | } else { XCTFail() } 61 | XCTAssertEqual(testSource.withTwoPropertiesKs.sealed, ExternalGenericWithTwoProperties(value1: "test1", value2: "test2")) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/ExternalNonGenericSealedClassesToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension ExternalNonGenericSealedClass { 11 | var withoutPropertyKs: ExternalNonGenericSealedClassKs { 12 | get { 13 | return ExternalNonGenericSealedClassKs(ExternalNonGenericWithoutProperty()) 14 | } 15 | } 16 | var withPropertyKs: ExternalNonGenericSealedClassKs { 17 | get { 18 | return ExternalNonGenericSealedClassKs(ExternalNonGenericWithProperty(value: "test")) 19 | } 20 | } 21 | } 22 | // <=== NEED TO GENERATE 23 | 24 | class ExternalNonGenericSealedClassToSwiftEnumTests: XCTestCase { 25 | private let testSource = ExternalNonGenericSealedClass() 26 | 27 | func testWithoutProperty() throws { 28 | if case .externalNonGenericWithoutProperty = testSource.withoutPropertyKs { } else { XCTFail() } 29 | XCTAssertEqual(testSource.withoutPropertyKs.sealed, ExternalNonGenericWithoutProperty()) 30 | } 31 | 32 | func testWithProperty() throws { 33 | if case .externalNonGenericWithProperty(let data) = testSource.withPropertyKs { 34 | XCTAssertEqual(data.value, "test") 35 | } else { XCTFail() } 36 | XCTAssertEqual(testSource.withPropertyKs.sealed, ExternalNonGenericWithProperty(value: "test")) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/GenericSealedClassesToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension TestGenericSealedClass { 11 | var withoutPropertyKs: GenericSealedClassKs { 12 | get { 13 | return GenericSealedClassKs(self.withoutProperty) 14 | } 15 | } 16 | var withOnePropertyTKs: GenericSealedClassKs { 17 | get { 18 | return GenericSealedClassKs(self.withOnePropertyT) 19 | } 20 | } 21 | var withOnePropertyUKs: GenericSealedClassKs { 22 | get { 23 | return GenericSealedClassKs(self.withOnePropertyU) 24 | } 25 | } 26 | var withTwoPropertiesKs: GenericSealedClassKs { 27 | get { 28 | return GenericSealedClassKs(self.withTwoProperties) 29 | } 30 | } 31 | } 32 | // <=== NEED TO GENERATE 33 | 34 | class GenericSealedClassToSwiftEnumTests: XCTestCase { 35 | private let testSource = TestGenericSealedClass() 36 | 37 | func testWithoutProperty() throws { 38 | if case .withoutProperty = testSource.withoutPropertyKs { } else { XCTFail() } 39 | XCTAssertEqual(testSource.withoutPropertyKs.sealed, GenericSealedClassWithoutProperty()) 40 | } 41 | 42 | func testWithOnePropertyT() throws { 43 | if case .withOnePropertyT(let data) = testSource.withOnePropertyTKs { 44 | XCTAssertEqual(data.value, "test") 45 | } else { XCTFail() } 46 | XCTAssertEqual(testSource.withOnePropertyTKs.sealed, GenericSealedClassWithOnePropertyT(value: "test")) 47 | } 48 | 49 | func testWithOnePropertyU() throws { 50 | if case .withOnePropertyU(let data) = testSource.withOnePropertyUKs { 51 | XCTAssertEqual(data.value, "test") 52 | } else { XCTFail() } 53 | XCTAssertEqual(testSource.withOnePropertyUKs.sealed, GenericSealedClassWithOnePropertyU(value: "test")) 54 | } 55 | 56 | func testWithTwoProperties() throws { 57 | if case .withTwoProperties(let data) = testSource.withTwoPropertiesKs { 58 | XCTAssertEqual(data.value1, "test1") 59 | XCTAssertEqual(data.value2, "test2") 60 | } else { XCTFail() } 61 | XCTAssertEqual(testSource.withTwoPropertiesKs.sealed, GenericSealedClassWithTwoProperties(value1: "test1", value2: "test2")) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/NonGenericSealedClassesToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension NonGenericSealedClass { 11 | var withoutPropertyKs: NonGenericSealedClassKs { 12 | get { 13 | return NonGenericSealedClassKs(WithoutProperty()) 14 | } 15 | } 16 | var withPropertyKs: NonGenericSealedClassKs { 17 | get { 18 | return NonGenericSealedClassKs(WithProperty(value: "test")) 19 | } 20 | } 21 | } 22 | // <=== NEED TO GENERATE 23 | 24 | class NonGenericSealedClassToSwiftEnumTests: XCTestCase { 25 | private let testSource = NonGenericSealedClass() 26 | 27 | func testWithoutProperty() throws { 28 | if case .withoutProperty = testSource.withoutPropertyKs { } else { XCTFail() } 29 | XCTAssertEqual(testSource.withoutPropertyKs.sealed, NonGenericSealedClass.WithoutProperty()) 30 | } 31 | 32 | func testWithProperty() throws { 33 | if case .withProperty(let data) = testSource.withPropertyKs { 34 | XCTAssertEqual(data.value, "test") 35 | } else { XCTFail() } 36 | XCTAssertEqual(testSource.withPropertyKs.sealed, NonGenericSealedClass.WithProperty(value: "test")) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/PlatformExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | class PlatformExtensionsTests: XCTestCase { 10 | 11 | private var label: UILabel! 12 | 13 | override func setUp() { 14 | self.label = UILabel() 15 | self.label.text = "empty" 16 | } 17 | 18 | func testClassProviderExtension() throws { 19 | let classProvider: CDataProvider = CDataProvider(data: "data") 20 | label.fillByKotlin(provider: classProvider) 21 | 22 | XCTAssertEqual(label.text, "data") 23 | } 24 | 25 | func testExtensionWithoutArgs() throws { 26 | label.fillByKotlin() 27 | 28 | XCTAssertEqual(label.text, "filled by kotlin") 29 | } 30 | 31 | func testExtensionWithArg() throws { 32 | label.fillByKotlin(text: "test") 33 | 34 | XCTAssertEqual(label.text, "test") 35 | } 36 | 37 | func testInterfaceProviderExtension() throws { 38 | let interfaceProvider: IDataProvider = TestProvider() 39 | label.fillByKotlin(provider: interfaceProvider) 40 | 41 | XCTAssertEqual(label.text, "test interface") 42 | } 43 | } 44 | 45 | class TestProvider: NSObject, IDataProvider { 46 | func getData() -> Any? { 47 | return "test interface" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/SealedClassToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension TestStateClassSource { 11 | var loadingKs: UIStateClassKs { 12 | get { 13 | return UIStateClassKs(self.loading) 14 | } 15 | } 16 | var emptyKs: UIStateClassKs { 17 | get { 18 | return UIStateClassKs(self.empty) 19 | } 20 | } 21 | var errorKs: UIStateClassKs { 22 | get { 23 | return UIStateClassKs(self.error) 24 | } 25 | } 26 | var dataKs: UIStateClassKs { 27 | get { 28 | return UIStateClassKs(self.data) 29 | } 30 | } 31 | } 32 | // <=== NEED TO GENERATE 33 | 34 | class SealedClassToSwiftEnumTests: XCTestCase { 35 | private let testSource = TestStateClassSource() 36 | 37 | func testLoadingState() throws { 38 | if case .loading = testSource.loadingKs { } else { XCTFail() } 39 | } 40 | 41 | func testEmptyState() throws { 42 | if case .empty = testSource.emptyKs { } else { XCTFail() } 43 | } 44 | 45 | func testDataState() throws { 46 | if case .data(let data) = testSource.dataKs { 47 | XCTAssertEqual(data.value, "test") 48 | } else { XCTFail() } 49 | } 50 | 51 | func testErrorState() throws { 52 | if case .error(let error) = testSource.errorKs { 53 | XCTAssertTrue(error.throwable is KotlinIllegalStateException) 54 | } else { XCTFail() } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/ios-app/Tests/SealedInterfaceToSwiftEnumTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import XCTest 6 | @testable import MultiPlatformLibrary 7 | @testable import MultiPlatformLibrarySwift 8 | 9 | // ===> NEED TO GENERATE 10 | extension TestStateSource { 11 | var loadingKs: UIStateKs { 12 | get { 13 | return UIStateKs(self.loading) 14 | } 15 | } 16 | var emptyKs: UIStateKs { 17 | get { 18 | return UIStateKs(self.empty) 19 | } 20 | } 21 | var errorKs: UIStateKs { 22 | get { 23 | return UIStateKs(self.error) 24 | } 25 | } 26 | var dataKs: UIStateKs { 27 | get { 28 | return UIStateKs(self.data) 29 | } 30 | } 31 | } 32 | // <=== NEED TO GENERATE 33 | 34 | class SealedToSwiftEnumTests: XCTestCase { 35 | private let testSource = TestStateSource() 36 | 37 | func testLoadingState() throws { 38 | if case .loading = testSource.loadingKs { } else { XCTFail() } 39 | XCTAssertTrue(testSource.loadingKs.sealed is UIStateLoading) 40 | } 41 | 42 | func testEmptyState() throws { 43 | if case .empty = testSource.emptyKs { } else { XCTFail() } 44 | XCTAssertTrue(testSource.emptyKs.sealed is UIStateEmpty) 45 | } 46 | 47 | func testDataState() throws { 48 | if case .data(let data) = testSource.dataKs { 49 | XCTAssertEqual(data.value, "test") 50 | } else { XCTFail() } 51 | XCTAssertTrue(testSource.dataKs.sealed is UIStateData) 52 | } 53 | 54 | func testErrorState() throws { 55 | if case .error(let error) = testSource.errorKs { 56 | XCTAssertTrue(error.throwable is KotlinIllegalStateException) 57 | } else { XCTFail() } 58 | XCTAssertTrue(testSource.errorKs.sealed is UIStateError) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sample/ios-app/ios-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/ios-app/ios-app.xcodeproj/xcshareddata/xcschemes/ios-app.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /sample/ios-app/ios-app.xcodeproj/xcshareddata/xcschemes/pods-test.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /sample/ios-app/ios-app.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/ios-app/ios-app.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | 7 | @main 8 | class AppDelegate: UIResponder, UIApplicationDelegate { 9 | 10 | 11 | 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | // Override point for customization after application launch. 14 | return true 15 | } 16 | 17 | // MARK: UISceneSession Lifecycle 18 | 19 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 20 | // Called when a new scene session is being created. 21 | // Use this method to select a configuration to create the new scene with. 22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 23 | } 24 | 25 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 26 | // Called when the user discards a scene session. 27 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 28 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 29 | } 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | 7 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 8 | 9 | var window: UIWindow? 10 | 11 | 12 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 13 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 14 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 15 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 16 | guard let _ = (scene as? UIWindowScene) else { return } 17 | } 18 | 19 | func sceneDidDisconnect(_ scene: UIScene) { 20 | // Called as the scene is being released by the system. 21 | // This occurs shortly after the scene enters the background, or when its session is discarded. 22 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 23 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 24 | } 25 | 26 | func sceneDidBecomeActive(_ scene: UIScene) { 27 | // Called when the scene has moved from an inactive state to an active state. 28 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 29 | } 30 | 31 | func sceneWillResignActive(_ scene: UIScene) { 32 | // Called when the scene will move from an active state to an inactive state. 33 | // This may occur due to temporary interruptions (ex. an incoming phone call). 34 | } 35 | 36 | func sceneWillEnterForeground(_ scene: UIScene) { 37 | // Called as the scene transitions from the background to the foreground. 38 | // Use this method to undo the changes made on entering the background. 39 | } 40 | 41 | func sceneDidEnterBackground(_ scene: UIScene) { 42 | // Called as the scene transitions from the foreground to the background. 43 | // Use this method to save data, release shared resources, and store enough scene-specific state information 44 | // to restore the scene back to its current state. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /sample/ios-app/pods-test/ViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | import shared 7 | import sharedSwift 8 | 9 | class ViewController: UIViewController { 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | 14 | let testSource = TestStateSource() 15 | let state = UIStateKs(testSource.error) 16 | 17 | switch(state) { 18 | case .empty: 19 | print("empty state") 20 | break 21 | case .loading: 22 | print("loading state") 23 | break 24 | case .data(let obj): 25 | print("data state with \(obj.value ?? "nil")") 26 | break 27 | case .error(let obj): 28 | print("error state with \(obj.throwable)") 29 | break 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /sample/ios-app/src/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | 7 | @UIApplicationMain 8 | class AppDelegate: NSObject, UIApplicationDelegate { 9 | 10 | var window: UIWindow? 11 | 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 13 | return true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/ios-app/src/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/ios-app/src/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/ios-app/src/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | moko-kswift 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(BUNDLE_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSMainStoryboardFile 33 | Main 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIRequiresFullScreen 43 | 44 | UIStatusBarHidden 45 | 46 | UIStatusBarHidden~ipad 47 | 48 | UIStatusBarStyle 49 | UIStatusBarStyleLightContent 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationLandscapeRight 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationPortraitUpsideDown 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /sample/ios-app/src/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sample/ios-app/src/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sample/ios-app/src/TestViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | import MultiPlatformLibrary 7 | import MultiPlatformLibrarySwift 8 | 9 | class TestViewController: UIViewController { 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/mpp-library-pods/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("dev.icerock.moko.gradle.detekt") 8 | id("org.jetbrains.kotlin.native.cocoapods") 9 | id("dev.icerock.moko.kswift") 10 | } 11 | 12 | // CocoaPods requires the podspec to have a version. 13 | version = "1.0" 14 | 15 | android { 16 | sourceSets.main.manifest.srcFile("src/androidMain/AndroidManifest.xml") 17 | } 18 | 19 | kotlin { 20 | cocoapods { 21 | // Configure fields required by CocoaPods. 22 | summary = "Some description for a Kotlin/Native module" 23 | homepage = "Link to a Kotlin/Native module homepage" 24 | frameworkName = "shared" 25 | } 26 | 27 | sourceSets { 28 | commonMain { 29 | dependencies { 30 | implementation(projects.kswiftRuntime) 31 | } 32 | } 33 | } 34 | } 35 | 36 | kswift { 37 | install(dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature.factory) { 38 | it.filter = it.includeFilter("one", "two") 39 | } 40 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature.factory) 41 | } 42 | -------------------------------------------------------------------------------- /sample/mpp-library-pods/mpp_library_pods.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'mpp_library_pods' 3 | spec.version = '1.0' 4 | spec.homepage = 'Link to a Kotlin/Native module homepage' 5 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 6 | spec.authors = '' 7 | spec.license = '' 8 | spec.summary = 'Some description for a Kotlin/Native module' 9 | 10 | spec.vendored_frameworks = "build/cocoapods/framework/shared.framework" 11 | spec.libraries = "c++" 12 | spec.module_name = "#{spec.name}_umbrella" 13 | 14 | 15 | 16 | 17 | 18 | spec.pod_target_xcconfig = { 19 | 'KOTLIN_PROJECT_PATH' => ':sample:mpp-library-pods', 20 | 'PRODUCT_MODULE_NAME' => 'mpp_library_pods', 21 | } 22 | 23 | spec.script_phases = [ 24 | { 25 | :name => 'Build mpp_library_pods', 26 | :execution_position => :before_compile, 27 | :shell_path => '/bin/sh', 28 | :script => <<-SCRIPT 29 | if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then 30 | echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\"" 31 | exit 0 32 | fi 33 | set -ev 34 | REPO_ROOT="$PODS_TARGET_SRCROOT" 35 | "$REPO_ROOT/../../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ 36 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ 37 | -Pkotlin.native.cocoapods.archs="$ARCHS" \ 38 | -Pkotlin.native.cocoapods.configuration=$CONFIGURATION 39 | SCRIPT 40 | } 41 | ] 42 | end -------------------------------------------------------------------------------- /sample/mpp-library-pods/mpp_library_podsSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'mpp_library_podsSwift' 3 | spec.version = '1.0' 4 | spec.homepage = 'Link to a Kotlin/Native module homepage' 5 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 6 | spec.authors = '' 7 | spec.license = '' 8 | spec.summary = 'Some description for a Kotlin/Native module' 9 | spec.module_name = "sharedSwift" 10 | 11 | spec.static_framework = true 12 | spec.dependency 'mpp_library_pods' 13 | spec.source_files = "build/cocoapods/framework/sharedSwift/**/*.{h,m,swift}" 14 | end -------------------------------------------------------------------------------- /sample/mpp-library-pods/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/mpp-library-pods/src/commonMain/kotlin/com/icerockdev/library/TestStateSource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | class TestStateSource { 8 | val loading: UIState = UIState.Loading 9 | val empty: UIState = UIState.Empty 10 | val data: UIState = UIState.Data(value = "test") 11 | val error: UIState = UIState.Error(throwable = IllegalStateException("some error")) 12 | } 13 | -------------------------------------------------------------------------------- /sample/mpp-library-pods/src/commonMain/kotlin/com/icerockdev/library/UIState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | sealed interface UIState { 8 | object Loading : UIState 9 | object Empty : UIState 10 | data class Data(val value: T) : UIState 11 | data class Error(val throwable: Throwable) : UIState 12 | } 13 | -------------------------------------------------------------------------------- /sample/mpp-library-pods/src/iosMain/kotlin/com/icerockdev/library/PlatformExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | import platform.UIKit.UILabel 8 | 9 | fun UILabel.fillByKotlin() { 10 | this.text = "filled by kotlin" 11 | } 12 | -------------------------------------------------------------------------------- /sample/mpp-library/MultiPlatformLibrary.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'MultiPlatformLibrary' 3 | spec.version = '0.1.0' 4 | spec.homepage = 'Link to a Kotlin/Native module homepage' 5 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 6 | spec.authors = 'IceRock Development' 7 | spec.license = '' 8 | spec.summary = 'Shared code between iOS and Android' 9 | 10 | spec.vendored_frameworks = "build/cocoapods/framework/#{spec.name}.framework" 11 | spec.libraries = "c++" 12 | spec.module_name = "#{spec.name}_umbrella" 13 | 14 | spec.ios.deployment_target = '11.0' 15 | spec.osx.deployment_target = '10.6' 16 | 17 | spec.pod_target_xcconfig = { 18 | 'KOTLIN_FRAMEWORK_BUILD_TYPE[config=*ebug]' => 'debug', 19 | 'KOTLIN_FRAMEWORK_BUILD_TYPE[config=*elease]' => 'release', 20 | 'CURENT_SDK[sdk=iphoneos*]' => 'iphoneos', 21 | 'CURENT_SDK[sdk=iphonesimulator*]' => 'iphonesimulator', 22 | 'CURENT_SDK[sdk=macosx*]' => 'macos' 23 | } 24 | 25 | spec.script_phases = [ 26 | { 27 | :name => 'Compile Kotlin/Native', 28 | :execution_position => :before_compile, 29 | :shell_path => '/bin/sh', 30 | :script => <<-SCRIPT 31 | if [ "$KOTLIN_FRAMEWORK_BUILD_TYPE" == "debug" ]; then 32 | CONFIG="Debug" 33 | else 34 | CONFIG="Release" 35 | fi 36 | 37 | if [ "$CURENT_SDK" == "iphoneos" ]; then 38 | TARGET="Ios" 39 | ARCH="Arm64" 40 | elif [ "$CURENT_SDK" == "macos" ]; then 41 | TARGET="Macos" 42 | if [ "$NATIVE_ARCH" == "arm64" ]; then 43 | ARCH="Arm64" 44 | else 45 | ARCH="X64" 46 | fi 47 | else 48 | if [ "$NATIVE_ARCH" == "arm64" ]; then 49 | TARGET="IosSimulator" 50 | ARCH="Arm64" 51 | else 52 | TARGET="Ios" 53 | ARCH="X64" 54 | fi 55 | fi 56 | 57 | MPP_PROJECT_ROOT="$SRCROOT/../../mpp-library" 58 | GRADLE_TASK="syncMultiPlatformLibrary${CONFIG}Framework${TARGET}${ARCH}" 59 | 60 | "$MPP_PROJECT_ROOT/../../gradlew" -p "$MPP_PROJECT_ROOT" "$GRADLE_TASK" 61 | SCRIPT 62 | } 63 | ] 64 | end -------------------------------------------------------------------------------- /sample/mpp-library/MultiPlatformLibrarySwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'MultiPlatformLibrarySwift' 3 | spec.version = '1.0' 4 | spec.homepage = 'Link to a Kotlin/Native module homepage' 5 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 6 | spec.authors = '' 7 | spec.license = '' 8 | spec.summary = 'Some description for a Kotlin/Native module' 9 | spec.module_name = "MultiPlatformLibrarySwift" 10 | 11 | spec.static_framework = false 12 | spec.dependency 'MultiPlatformLibrary' 13 | spec.source_files = "build/cocoapods/framework/MultiPlatformLibrarySwift/**/*.{h,m,swift}" 14 | end -------------------------------------------------------------------------------- /sample/mpp-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("dev.icerock.mobile.multiplatform.ios-framework") 8 | id("dev.icerock.moko.gradle.detekt") 9 | id("dev.icerock.moko.kswift") 10 | } 11 | 12 | kswift { 13 | install(dev.icerock.moko.kswift.plugin.feature.PlatformExtensionFunctionsFeature) 14 | install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature) 15 | install(dev.icerock.moko.kswift.plugin.feature.DataClassCopyFeature) 16 | 17 | excludeLibrary("kotlinx-coroutines-core") 18 | 19 | projectPodspecName.set("MultiPlatformLibrary") 20 | iosDeploymentTarget.set("11.0") 21 | } 22 | 23 | dependencies { 24 | commonMainApi(libs.coroutines) 25 | 26 | commonMainApi(projects.kswiftRuntime) 27 | } 28 | 29 | framework { 30 | export(libs.coroutines) 31 | export(projects.kswiftRuntime) 32 | } 33 | -------------------------------------------------------------------------------- /sample/mpp-library/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/DataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | data class DataClass( 8 | val stringValue: String, 9 | val optionalStringValue: String?, 10 | val intValue: Int, 11 | val optionalIntValue: Int?, 12 | val booleanValue: Boolean, 13 | val optionalBooleanValue: Boolean? 14 | ) 15 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/DataClassWithCollections.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | data class DataClassWithCollections( 8 | val stringValues: List, 9 | val optionalStringValues: List?, 10 | val intValues: Set, 11 | val optionalIntValues: Set?, 12 | val booleanValues: Map, 13 | val optionalBooleanValues: Map? 14 | ) 15 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/ExcludedSealed.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | import dev.icerock.moko.kswift.KSwiftExclude 8 | 9 | @KSwiftExclude 10 | sealed interface ExcludedSealed { 11 | object V1 : ExcludedSealed 12 | object V2 : ExcludedSealed 13 | } 14 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/Extensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | interface SomeInterface 8 | 9 | fun SomeInterface.extensionOnInterface() = Unit 10 | 11 | internal fun SomeInterface.internalExtensionOnInterface() = Unit 12 | 13 | val String.extensionPropertyOnString: String get() = "123" 14 | 15 | fun String.extensionFunctionOnString() = Unit 16 | 17 | fun List.extensionOnList(): String = joinToString() 18 | 19 | class SomeClass { 20 | fun test() = Unit 21 | 22 | companion object 23 | } 24 | 25 | fun SomeClass.Companion.extensionOnCompanion() = Unit 26 | 27 | fun SomeClass.extensionOnKotlinClass() = Unit 28 | 29 | fun IntProgression.Companion.extensionOnKotlinCompanion() = Unit 30 | 31 | fun String.stringToThrowable(): Throwable = TODO() 32 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/ExternalGenericSealedClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | /** 8 | * This sealed class was created to test alongside the [ExternalNonGenericSealedClass] type to ensure that kswift 9 | * creates the correct enum types for generic sealed classes with multiple type parameters. 10 | * Here, subclasses are defined out of the body of the parent class. 11 | * See [GenericSealedClass] to see an example of nested subclasses 12 | */ 13 | sealed class ExternalGenericSealedClass 14 | 15 | /** A test `object` with no parameters and hard-coded types. */ 16 | object ExternalGenericWithoutProperty : ExternalGenericSealedClass() 17 | 18 | /** 19 | * A test `data class` with one parameter, one generic type, and one hard-coded type. Hard-codes 20 | * the second type parameter (U). 21 | */ 22 | data class ExternalGenericWithOnePropertyT(val value: T) : 23 | ExternalGenericSealedClass() 24 | 25 | /** 26 | * A test `data class` with one parameter, one generic type, and one hard-coded type. Hard-codes 27 | * the first type parameter (T). 28 | */ 29 | data class ExternalGenericWithOnePropertyU(val value: U) : 30 | ExternalGenericSealedClass() 31 | 32 | /** A test `data class` with two parameters and two generic types. */ 33 | data class ExternalGenericWithTwoProperties(val value1: T, val value2: U) : 34 | ExternalGenericSealedClass() 35 | 36 | class TestExternalGenericSealedClass { 37 | val withoutProperty: ExternalGenericSealedClass = ExternalGenericWithoutProperty 38 | val withOnePropertyT: ExternalGenericSealedClass = 39 | ExternalGenericWithOnePropertyT("test") 40 | val withOnePropertyU: ExternalGenericSealedClass = 41 | ExternalGenericWithOnePropertyU("test") 42 | val withTwoProperties: ExternalGenericSealedClass = 43 | ExternalGenericWithTwoProperties("test1", "test2") 44 | } 45 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/ExternalNonGenericSealedClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | /** 8 | * This sealed class was created to test alongside the [ExternalGenericSealedClass] type to ensure that 9 | * kswift creates the correct enum types for both generic and non-generic sealed classes. 10 | * Here, subclasses are defined out of the body of the parent class. 11 | * See [NonGenericSealedClass] to see an example of nested subclasses 12 | */ 13 | sealed class ExternalNonGenericSealedClass 14 | 15 | /** A test `object` that has no property. */ 16 | object ExternalNonGenericWithoutProperty : ExternalNonGenericSealedClass() 17 | 18 | /** A test `data class` with an associated value. */ 19 | data class ExternalNonGenericWithProperty(val value: String) : ExternalNonGenericSealedClass() 20 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/GenericSealedClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | /** 8 | * This sealed class was created to test alongside the `NonGenericSealedClass` type to ensure that kswift 9 | * creates the correct enum types for generic sealed classes with multiple type parameters. 10 | */ 11 | sealed class GenericSealedClass { 12 | /** A test `object` with no parameters and hard-coded types. */ 13 | object WithoutProperty : GenericSealedClass() 14 | 15 | /** 16 | * A test `data class` with one parameter, one generic type, and one hard-coded type. Hard-codes 17 | * the second type parameter (U). 18 | */ 19 | data class WithOnePropertyT(val value: T) : GenericSealedClass() 20 | 21 | /** 22 | * A test `data class` with one parameter, one generic type, and one hard-coded type. Hard-codes 23 | * the first type parameter (T). 24 | */ 25 | data class WithOnePropertyU(val value: U) : GenericSealedClass() 26 | 27 | /** A test `data class` with two parameters and two generic types. */ 28 | data class WithTwoProperties(val value1: T, val value2: U) : GenericSealedClass() 29 | } 30 | 31 | class TestGenericSealedClass { 32 | val withoutProperty: GenericSealedClass = GenericSealedClass.WithoutProperty 33 | val withOnePropertyT: GenericSealedClass = GenericSealedClass.WithOnePropertyT("test") 34 | val withOnePropertyU: GenericSealedClass = GenericSealedClass.WithOnePropertyU("test") 35 | val withTwoProperties: GenericSealedClass = GenericSealedClass.WithTwoProperties("test1", "test2") 36 | } 37 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/NonGenericSealedClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | /** 8 | * This sealed class was created to test alongside the `GenericSealedClass` type to ensure that 9 | * kswift creates the correct enum types for both generic and non-generic sealed classes. 10 | */ 11 | sealed class NonGenericSealedClass { 12 | /** A test `object` that has no property. */ 13 | object WithoutProperty : NonGenericSealedClass() 14 | 15 | /** A test `data class` with an associated value. */ 16 | data class WithProperty(val value: String) : NonGenericSealedClass() 17 | } 18 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/Status.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | sealed class Status { 8 | data class Success(val data: T) : Status() 9 | data class Failure(val exception: Exception) : Status() 10 | } 11 | 12 | val successStatus: Status = Status.Success("hi!") 13 | val failureStatus: Status = Status.Failure(IllegalArgumentException("illegal")) 14 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/UIState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | sealed interface UIState { 8 | object Loading : UIState 9 | object Empty : UIState 10 | data class Data(val value: T) : UIState 11 | data class Error(val throwable: Throwable) : UIState 12 | } 13 | 14 | class TestStateSource { 15 | val loading: UIState = UIState.Loading 16 | val empty: UIState = UIState.Empty 17 | val data: UIState = UIState.Data(value = "test") 18 | val error: UIState = UIState.Error(throwable = IllegalStateException("some error")) 19 | } 20 | 21 | sealed class UIStateClass { 22 | object Loading : UIStateClass() 23 | object Empty : UIStateClass() 24 | data class Data(val value: T) : UIStateClass() 25 | data class Error(val throwable: Throwable) : UIStateClass() 26 | } 27 | 28 | class TestStateClassSource { 29 | val loading: UIStateClass = UIStateClass.Loading 30 | val empty: UIStateClass = UIStateClass.Empty 31 | val data: UIStateClass = UIStateClass.Data(value = "test") 32 | val error: UIStateClass = UIStateClass.Error(throwable = IllegalStateException("some error")) 33 | } 34 | 35 | internal sealed interface InternalSealedTest { 36 | object Case : InternalSealedTest 37 | } 38 | 39 | sealed class InternalInsideSealedTest { 40 | internal object InternalSealed : InternalInsideSealedTest() 41 | } 42 | 43 | sealed class PublicInternalInsideSealedTest { 44 | internal object InternalSealed : PublicInternalInsideSealedTest() 45 | object PublicSealed : PublicInternalInsideSealedTest() 46 | } 47 | -------------------------------------------------------------------------------- /sample/mpp-library/src/iosMain/kotlin/com/icerockdev/library/PlatformExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | import dev.icerock.moko.kswift.KSwiftExclude 8 | import dev.icerock.moko.kswift.KSwiftOverrideName 9 | import platform.Foundation.NSNotificationCenter 10 | import platform.UIKit.UIControl 11 | import platform.UIKit.UIControlEvents 12 | import platform.UIKit.UILabel 13 | 14 | fun UILabel.fillByKotlin() { 15 | this.text = "filled by kotlin" 16 | } 17 | 18 | fun UILabel.fillByKotlin(text: String) { 19 | this.text = text 20 | } 21 | 22 | interface IDataProvider { 23 | fun getData(): T 24 | } 25 | 26 | fun UILabel.fillByKotlin( 27 | @KSwiftOverrideName("provider_") 28 | provider: IDataProvider 29 | ) { 30 | this.text = provider.getData() 31 | } 32 | 33 | class CDataProvider(private val data: T) : IDataProvider { 34 | override fun getData(): T = data 35 | } 36 | 37 | fun UILabel.fillByKotlin(provider: CDataProvider) { 38 | this.text = provider.getData() 39 | } 40 | 41 | @KSwiftExclude 42 | fun UILabel.excludedFun() = Unit 43 | 44 | fun T.setEventHandler( 45 | event: UIControlEvents, 46 | lambda: (T) -> Unit 47 | ): Closeable { 48 | TODO() 49 | } 50 | 51 | /* 52 | fun NSNotificationCenter.setEventHandler( 53 | notification: NSNotificationName, 54 | ref: T, 55 | lambda: T.() -> Unit 56 | ): Closeable { 57 | TODO() 58 | } 59 | */ 60 | 61 | fun NSNotificationCenter.Companion.extensionOnPlatformCompanion() = Unit 62 | -------------------------------------------------------------------------------- /sample/mpp-library/src/iosMain/kotlin/com/icerockdev/library/UILabelExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.flow.StateFlow 9 | import kotlinx.coroutines.launch 10 | import platform.UIKit.UILabel 11 | 12 | class LiveData 13 | 14 | interface Closeable 15 | 16 | fun UILabel.bindText( 17 | liveData: LiveData, 18 | formatter: (String) -> String 19 | ): Closeable { 20 | TODO() 21 | } 22 | 23 | fun UILabel.bindText( 24 | liveData: LiveData 25 | ): Closeable { 26 | TODO() 27 | } 28 | 29 | class CFlow(private val stateFlow: StateFlow) : StateFlow by stateFlow 30 | 31 | fun UILabel.bindText(coroutineScope: CoroutineScope, flow: CFlow) { 32 | val label = this 33 | coroutineScope.launch { 34 | label.text = flow.value 35 | flow.collect { label.text = it } 36 | } 37 | } 38 | 39 | fun UILabel.bindGenericText(liveData: LiveData): Closeable { 40 | TODO() 41 | } 42 | 43 | fun UILabel.bindGenericAny(liveData: LiveData): Closeable { 44 | TODO() 45 | } 46 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | rootProject.name = "moko-kswift" 6 | 7 | enableFeaturePreview("VERSION_CATALOGS") 8 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 9 | 10 | dependencyResolutionManagement { 11 | repositories { 12 | mavenCentral() 13 | google() 14 | } 15 | } 16 | 17 | includeBuild("kswift-gradle-plugin") 18 | 19 | include(":kswift-runtime") 20 | include(":sample:android-app") 21 | include(":sample:mpp-library") 22 | include(":sample:mpp-library-pods") 23 | --------------------------------------------------------------------------------