├── .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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/copyright/IceRock.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 | 
2 | [](http://www.apache.org/licenses/LICENSE-2.0) [ ](https://repo1.maven.org/maven2/dev/icerock/moko/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 | 
11 |
12 | ## Kotlin extensions for K/N platform classes
13 |
14 | 
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