├── .github └── workflows │ └── create-release.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main └── kotlin ├── AndroidLibrary.kt ├── GradlePlugin.kt ├── KaptLibrary.kt ├── KotlinNativeExportable.kt ├── MultiPlatformLibrary.kt ├── MultiPlatformModule.kt └── dev └── icerock └── gradle ├── AndroidManifestPlugin.kt ├── AndroidSourcesPlugin.kt ├── AppleFrameworkPlugin.kt ├── CocoaPodInfo.kt ├── CocoapodsConfig.kt ├── CocoapodsPlugin.kt ├── FrameworkConfig.kt ├── IosFrameworkPlugin.kt ├── LogOutputStream.kt ├── MobileMultiPlatformPlugin.kt ├── MobileTargetsPlugin.kt └── tasks └── CompileCocoaPod.kt /.github/workflows/create-release.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 to mavenCentral 14 | runs-on: ubuntu-latest 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 | steps: 22 | - uses: actions/checkout@v1 23 | - name: Set up JDK 11 24 | uses: actions/setup-java@v1 25 | with: 26 | java-version: 11 27 | - name: Publish to MavenCentral 28 | run: ./gradlew publishMavenJavaPublicationToOSSRHRepository 29 | - name: Publish to GradlePlugnPortal 30 | run: ./gradlew publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PLUGIN_PORTAL_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PLUGIN_PORTAL_SECRET }} 31 | release: 32 | name: Create release 33 | needs: publish 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Create Release 37 | id: create_release 38 | uses: actions/create-release@v1 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | commitish: ${{ github.ref }} 43 | tag_name: release/${{ github.event.inputs.version }} 44 | release_name: ${{ github.event.inputs.version }} 45 | body: "Will be filled later" 46 | draft: true 47 | -------------------------------------------------------------------------------- /.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 | local.gradle -------------------------------------------------------------------------------- /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 | ![mobile-multiplatform](https://user-images.githubusercontent.com/5010169/100611874-9aa17f80-3344-11eb-9737-c50ba63b0f6e.png) 2 | [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/dev.icerock/mobile-multiplatform) ](https://repo1.maven.org/maven2/dev/icerock/mobile-multiplatform) 3 | 4 | # Mobile Multiplatform gradle plugin 5 | This is a Gradle plugin for simple setup of Kotlin Multiplatform mobile Gradle modules. 6 | 7 | ## Setup 8 | `buildSrc/build.gradle.kts` 9 | ```kotlin 10 | repositories { 11 | google() 12 | gradlePluginPortal() 13 | } 14 | 15 | dependencies { 16 | implementation("dev.icerock:mobile-multiplatform:0.14.4") 17 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") 18 | implementation("com.android.tools.build:gradle:8.3.2") 19 | } 20 | ``` 21 | 22 | ## Usage 23 | ### Setup mobile targets without config 24 | `build.gradle.kts` 25 | ```kotlin 26 | plugins { 27 | id("com.android.library") 28 | id("org.jetbrains.kotlin.multiplatform") 29 | id("dev.icerock.mobile.multiplatform.targets") 30 | } 31 | ``` 32 | 33 | Plugin automatically setup android, ios targets. 34 | Android target also automatically configured with `dev.icerock.mobile.multiplatform.android-manifest` 35 | and `dev.icerock.mobile.multiplatform.android-sources` plugins. 36 | 37 | By default used `ios()` targets creation with intermediate source set `iosMain`. To disable it add 38 | into `gradle.properties` line: 39 | ``` 40 | mobile.multiplatform.useIosShortcut=false 41 | ``` 42 | 43 | Also by default `iosSimulatorArm64` target will be created (with connection to `iosMain` and 44 | `iosTest` if was used ios shortcut. To disable it add into `gradle.properties` line: 45 | ``` 46 | mobile.multiplatform.withoutIosSimulatorArm64=false 47 | ``` 48 | 49 | ### Setup AndroidManifest.xml in androidMain sourceSet 50 | `build.gradle.kts` 51 | ```kotlin 52 | plugins { 53 | id("dev.icerock.mobile.multiplatform.android-manifest") 54 | } 55 | ``` 56 | 57 | After enable this plugin you can move `AndroidManifest.xml` from `src/main/AndroidManifest.xml` to 58 | `src/androidMain/AndroidManifest.xml` 59 | 60 | ### Setup android sourceSets in android prefixed source sets 61 | `build.gradle.kts` 62 | ```kotlin 63 | plugins { 64 | id("dev.icerock.mobile.multiplatform.android-sources") 65 | } 66 | ``` 67 | 68 | After enable this plugin you can move android's `main` source set to `androidMain`, `release` 69 | to `androidRelease`, `test` to `androidTest` etc. 70 | 71 | ### Setup cocoapods integration for iOS 72 | `build.gradle.kts` 73 | ```kotlin 74 | plugins { 75 | id("dev.icerock.mobile.multiplatform.ios-framework") 76 | } 77 | ``` 78 | 79 | Plugin will setup `sync` gradle tasks in group `cocoapods` for `cocoapods` integration. 80 | 81 | Example of `podspec` for integration here - https://github.com/icerockdev/moko-template/blob/master/mpp-library/MultiPlatformLibrary.podspec 82 | 83 | #### Add artifacts to export 84 | ```kotlin 85 | // optional for export dependencies into framework header 86 | framework { 87 | export(project = project(":myproject")) 88 | export(kotlinNativeExportable = MultiPlatfomLibrary(<...>)) 89 | export(kotlinNativeExportable = MultiPlatfomModule(<...>)) 90 | export(arm64Dependency = "my.group:name-iosarm64:0.1.0", x64Dependency = "my.group:name-iosx64:0.1.0") 91 | export(artifact = "my.group:name:0.1.0") // common artifact 92 | export(provider = libs.myLib) // gradle 7 version catalog libraries accessors 93 | } 94 | ``` 95 | 96 | ### Setup cocoapods integration for all Apple frameworks 97 | `build.gradle.kts` 98 | ```kotlin 99 | plugins { 100 | id("dev.icerock.mobile.multiplatform.apple-framework") 101 | } 102 | ``` 103 | 104 | with `framework` configuration you can add dependencies to export (just like in iOS framework). 105 | 106 | ### Setup CocoaPods interop 107 | `build.gradle.kts` 108 | ```kotlin 109 | plugins { 110 | id("dev.icerock.mobile.multiplatform.cocoapods") 111 | } 112 | 113 | cocoaPods { 114 | podsProject = file("../ios-app/Pods/Pods.xcodeproj") // here should be path to your Pods project 115 | buildConfiguration = "dev-debug" // optional, default is "debug" 116 | 117 | pod("MBProgressHUD") // create cInterop and link with CocoaPod where schema and module is same 118 | pod(schema = "moko-widgets-flat", module = "mokoWidgetsFlat") // create cInterop and link with CocoaPod where schema and module is different 119 | pod(schema = "moko-widgets-flat", module = "mokoWidgetsFlat", onlyLink = true) // not create cInterop - just link framework with this CocoaPod 120 | } 121 | ``` 122 | 123 | Also path to Pods project and configuration can be set globally into `gradle.properties` 124 | ```properties 125 | mobile.multiplatform.podsProject=ios-app/Pods/Pods.xcodeproj 126 | mobile.multiplatform.podsConfiguration=dev-debug 127 | ``` 128 | `podsProject` should be relative path from root gradle project. 129 | 130 | ### Multiple plugins in one line (deprecated, saved for backward compatibility) 131 | ```kotlin 132 | plugins { 133 | id("dev.icerock.mobile.multiplatform") 134 | } 135 | ``` 136 | This line will enable: 137 | - `dev.icerock.mobile.multiplatform.cocoapods` 138 | - `dev.icerock.mobile.multiplatform.targets` 139 | - publish of android build variants - `release` and `debug` 140 | 141 | ### Definition of dependencies (deprecated, saved for backward compatibility) 142 | ```kotlin 143 | val mokoTime = MultiPlatformLibrary( 144 | android = "dev.icerock.moko:time-android:0.1.0", 145 | common = "dev.icerock.moko:time:0.1.0", 146 | iosX64 = "dev.icerock.moko:time-iosx64:0.1.0", 147 | iosArm64 = "dev.icerock.moko:time-iosarm64:0.1.0" 148 | ) 149 | 150 | val appCompat = AndroidLibrary( 151 | name = "androidx.appcompat:appcompat:1.1.0" 152 | ) 153 | 154 | val myFeature = MultiPlatformModule( 155 | name = ":mpp-library:feature:myFeature" 156 | ) 157 | ``` 158 | 159 | ### Setup dependencies (deprecated, saved for backward compatibility) 160 | `build.gradle.kts` 161 | ```kotlin 162 | dependencies { 163 | mppLibrary(mokoTime) 164 | androidLibrary(appCompat) 165 | mppModule(myFeature) 166 | } 167 | ``` 168 | 169 | ## License 170 | 171 | Copyright 2019 IceRock MAG Inc. 172 | 173 | Licensed under the Apache License, Version 2.0 (the "License"); 174 | you may not use this file except in compliance with the License. 175 | You may obtain a copy of the License at 176 | 177 | http://www.apache.org/licenses/LICENSE-2.0 178 | 179 | Unless required by applicable law or agreed to in writing, software 180 | distributed under the License is distributed on an "AS IS" BASIS, 181 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 182 | See the License for the specific language governing permissions and 183 | limitations under the License. 184 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import java.util.Base64 6 | 7 | plugins { 8 | `kotlin-dsl` 9 | id("org.gradle.maven-publish") 10 | id("signing") 11 | id("com.gradle.plugin-publish") version ("0.15.0") 12 | id("java-gradle-plugin") 13 | } 14 | 15 | 16 | group = "dev.icerock" 17 | version = libs.versions.mobileMultiplatformGradlePluginVersion.get() 18 | 19 | repositories { 20 | mavenCentral() 21 | google() 22 | } 23 | 24 | dependencies { 25 | implementation(kotlin("stdlib")) 26 | compileOnly(libs.androidGradlePlugin) 27 | compileOnly(libs.kotlinGradlePlugin) 28 | compileOnly(libs.kotlinCompilerEmbeddable) 29 | } 30 | 31 | java { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | withJavadocJar() 35 | withSourcesJar() 36 | } 37 | 38 | publishing { 39 | repositories.maven("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") { 40 | name = "OSSRH" 41 | 42 | credentials { 43 | username = System.getenv("OSSRH_USER") 44 | password = System.getenv("OSSRH_KEY") 45 | } 46 | } 47 | publications { 48 | register("mavenJava", MavenPublication::class) { 49 | from(components["java"]) 50 | pom { 51 | name.set("Mobile Multiplatform gradle plugin") 52 | description.set("Gradle plugin for simplify Kotlin Multiplatform Mobile configurations") 53 | url.set("https://github.com/icerockdev/mobile-multiplatform-gradle-plugin") 54 | licenses { 55 | license { 56 | name.set("Apache-2.0") 57 | distribution.set("repo") 58 | url.set("https://github.com/icerockdev/mobile-multiplatform-gradle-plugin/blob/master/LICENSE.md") 59 | } 60 | } 61 | 62 | developers { 63 | developer { 64 | id.set("Alex009") 65 | name.set("Aleksey Mikhailov") 66 | email.set("aleksey.mikhailov@icerockdev.com") 67 | } 68 | } 69 | 70 | scm { 71 | connection.set("scm:git:ssh://github.com/icerockdev/mobile-multiplatform-gradle-plugin.git") 72 | developerConnection.set("scm:git:ssh://github.com/icerockdev/mobile-multiplatform-gradle-plugin.git") 73 | url.set("https://github.com/icerockdev/mobile-multiplatform-gradle-plugin") 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | signing { 81 | val signingKeyId: String? = System.getenv("SIGNING_KEY_ID") 82 | val signingPassword: String? = System.getenv("SIGNING_PASSWORD") 83 | val signingKey: String? = System.getenv("SIGNING_KEY")?.let { base64Key -> 84 | String(Base64.getDecoder().decode(base64Key)) 85 | } 86 | 87 | if (signingKeyId != null) { 88 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) 89 | sign(publishing.publications) 90 | } 91 | } 92 | 93 | gradlePlugin { 94 | plugins { 95 | create("multiplatform") { 96 | id = "dev.icerock.mobile.multiplatform" 97 | displayName = "multiplatform" 98 | description = "Multiple plugins in one line (deprecated, saved for backward compatibility)" 99 | implementationClass = "dev.icerock.gradle.MobileMultiPlatformPlugin" 100 | } 101 | create("android-manifest") { 102 | id = "dev.icerock.mobile.multiplatform.android-manifest" 103 | displayName = "android-manifest" 104 | description = "After enable this plugin you can move AndroidManifest.xml from src/main/AndroidManifest.xml to src/androidMain/AndroidManifest.xml" 105 | implementationClass = "dev.icerock.gradle.AndroidManifestPlugin" 106 | } 107 | create("android-sources") { 108 | id = "dev.icerock.mobile.multiplatform.android-sources" 109 | displayName = "android-sources" 110 | description = "After enable this plugin you can move android's main source set to androidMain, release to androidRelease, test to androidTest etc." 111 | implementationClass = "dev.icerock.gradle.AndroidSourcesPlugin" 112 | } 113 | create("apple-framework") { 114 | id = "dev.icerock.mobile.multiplatform.apple-framework" 115 | displayName = "apple-framework" 116 | description = "After enable this plugin while using framework configuration you can add dependencies to export just like in iOS framework." 117 | implementationClass = "dev.icerock.gradle.AppleFrameworkPlugin" 118 | } 119 | create("cocoapods") { 120 | id = "dev.icerock.mobile.multiplatform.cocoapods" 121 | displayName = "cocoapods" 122 | description = "Setup CocoaPods interop. Path to Pods project and configuration can be set globally into gradle.properties" 123 | implementationClass = "dev.icerock.gradle.CocoapodsPlugin" 124 | } 125 | create("ios-framework") { 126 | id = "dev.icerock.mobile.multiplatform.ios-framework" 127 | displayName = "ios-framework" 128 | description = "Plugin will setup sync gradle tasks in group cocoapods for cocoapods integration. Example of podspec for integration here - https://github.com/icerockdev/moko-template/blob/master/mpp-library/MultiPlatformLibrary.podspec" 129 | implementationClass = "dev.icerock.gradle.IosFrameworkPlugin" 130 | } 131 | create("targets") { 132 | id = "dev.icerock.mobile.multiplatform.targets" 133 | displayName = "targets" 134 | description = "Plugin automatically setup android, ios targets. Android target also automatically configured with dev.icerock.mobile.multiplatform.android-manifest and dev.icerock.mobile.multiplatform.android-sources plugins." 135 | implementationClass = "dev.icerock.gradle.MobileTargetsPlugin" 136 | } 137 | } 138 | } 139 | 140 | pluginBundle { 141 | website = "https://github.com/icerockdev/mobile-multiplatform-gradle-plugin" 142 | vcsUrl = "https://github.com/icerockdev/mobile-multiplatform-gradle-plugin" 143 | description = "Gradle plugin for simplify Kotlin Multiplatform Mobile configurations" 144 | tags = listOf("kotlin", "kotlin-multiplatform") 145 | 146 | mavenCoordinates { 147 | groupId = project.group as String 148 | artifactId = project.name 149 | version = project.version as String 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m 2 | org.gradle.configureondemand=false 3 | org.gradle.parallel=true 4 | 5 | kotlin.code.style=official 6 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlinVersion = "1.6.10" 3 | androidGradlePluginVerison = "7.0.4" 4 | publishPluginVersion = "0.15.0" 5 | mobileMultiplatformGradlePluginVersion = "0.14.4" 6 | 7 | [libraries] 8 | androidGradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePluginVerison" } 9 | kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion"} 10 | kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlinVersion" } 11 | publisPlugin = { module = "com.gradle.publish:plugin-publish-plugin", version.ref = "publishPluginVersion"} 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/mobile-multiplatform-gradle-plugin/0d86f0442f29fca50f826b2c64a93614543e1cf1/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.3.3-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 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | enableFeaturePreview("VERSION_CATALOGS") 5 | 6 | rootProject.name = "mobile-multiplatform" 7 | -------------------------------------------------------------------------------- /src/main/kotlin/AndroidLibrary.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.kotlin.dsl.DependencyHandlerScope 6 | 7 | data class AndroidLibrary(val name: String) 8 | 9 | fun DependencyHandlerScope.androidLibrary(configuration: String, androidLibrary: AndroidLibrary) { 10 | "android$configuration"(androidLibrary.name) 11 | } 12 | 13 | fun DependencyHandlerScope.androidLibrary(androidLibrary: AndroidLibrary) { 14 | androidLibrary(configuration = "MainImplementation", androidLibrary = androidLibrary) 15 | } 16 | 17 | fun DependencyHandlerScope.androidTestLibrary(androidLibrary: AndroidLibrary) { 18 | androidLibrary(configuration = "TestImplementation", androidLibrary = androidLibrary) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/GradlePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.api.artifacts.Dependency 6 | import org.gradle.kotlin.dsl.DependencyHandlerScope 7 | import org.gradle.plugin.use.PluginDependenciesSpec 8 | import org.gradle.plugin.use.PluginDependencySpec 9 | 10 | data class GradlePlugin( 11 | val id: String, 12 | val module: String? = null, 13 | val version: String? = null 14 | ) 15 | 16 | fun DependencyHandlerScope.plugin(gradlePlugin: GradlePlugin): Dependency? { 17 | return gradlePlugin.module?.let { "classpath"(it) } 18 | } 19 | 20 | fun PluginDependenciesSpec.plugin(gradlePlugin: GradlePlugin): PluginDependencySpec { 21 | val spec = id(gradlePlugin.id) 22 | gradlePlugin.version?.also { spec.version(it) } 23 | return spec 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/KaptLibrary.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.kotlin.dsl.DependencyHandlerScope 6 | 7 | data class KaptLibrary(val name: String) 8 | 9 | fun DependencyHandlerScope.kaptLibrary(kaptLibrary: KaptLibrary) { 10 | "kapt"(kaptLibrary.name) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/KotlinNativeExportable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.api.Project 6 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 7 | 8 | interface KotlinNativeExportable { 9 | fun export(project: Project, framework: Framework) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/MultiPlatformLibrary.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.api.Project 6 | import org.gradle.kotlin.dsl.DependencyHandlerScope 7 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 8 | import org.jetbrains.kotlin.konan.target.KonanTarget 9 | 10 | data class MultiPlatformLibrary( 11 | val android: String? = null, 12 | val common: String, 13 | val iosX64: String? = null, 14 | val iosArm32: String? = null, 15 | val iosArm64: String? = null, 16 | val iosSimulatorArm64: String? = null, 17 | val macosX64: String? = null, 18 | val macosArm64: String? = null, 19 | val tvosX64: String? = null, 20 | val tvosArm64: String? = null, 21 | val tvosSimulatorArm64: String? = null, 22 | val watchosX86: String? = null, 23 | val watchosX64: String? = null, 24 | val watchosArm32: String? = null, 25 | val watchosArm64: String? = null, 26 | val watchosSimulatorArm64: String? = null, 27 | ) : KotlinNativeExportable { 28 | 29 | override fun export(project: Project, framework: Framework) { 30 | val arch = framework.target.konanTarget 31 | val exportArtifact: String? = when (arch) { 32 | KonanTarget.IOS_X64 -> iosX64 33 | KonanTarget.IOS_ARM32 -> iosArm32 34 | KonanTarget.IOS_ARM64 -> iosArm64 35 | KonanTarget.IOS_SIMULATOR_ARM64 -> iosSimulatorArm64 36 | KonanTarget.MACOS_X64 -> macosX64 37 | KonanTarget.MACOS_ARM64 -> macosArm64 38 | KonanTarget.TVOS_ARM64 -> tvosArm64 39 | KonanTarget.TVOS_X64 -> tvosX64 40 | KonanTarget.TVOS_SIMULATOR_ARM64 -> tvosSimulatorArm64 41 | KonanTarget.WATCHOS_X86 -> watchosX86 42 | KonanTarget.WATCHOS_X64 -> watchosX64 43 | KonanTarget.WATCHOS_ARM32 -> watchosArm32 44 | KonanTarget.WATCHOS_ARM64 -> watchosArm64 45 | KonanTarget.WATCHOS_SIMULATOR_ARM64 -> watchosSimulatorArm64 46 | else -> return 47 | } 48 | exportArtifact?.let { framework.export(it) } 49 | } 50 | } 51 | 52 | fun DependencyHandlerScope.mppLibrary(configuration: String, library: MultiPlatformLibrary) { 53 | library.android?.let { "android$configuration"(it) } 54 | "common$configuration"(library.common) 55 | // ios 56 | library.iosArm32?.let { "iosArm32$configuration"(it) } 57 | library.iosArm64?.let { "iosArm64$configuration"(it) } 58 | library.iosSimulatorArm64?.let { "iosSimulatorArm64$configuration"(it) } 59 | library.iosX64?.let { "iosX64$configuration"(it) } 60 | // macos 61 | library.macosX64?.let { "macosX64$configuration"(it) } 62 | library.macosArm64?.let { "macosArm64$configuration"(it) } 63 | // tvos 64 | library.tvosArm64?.let { "tvosArm64$configuration"(it) } 65 | library.tvosX64?.let { "tvosX64$configuration"(it) } 66 | library.tvosSimulatorArm64?.let { "tvosSimulatorArm64$configuration"(it) } 67 | // watchos 68 | library.watchosX86?.let { "watchosX86$configuration"(it) } 69 | library.watchosX64?.let { "watchosX64$configuration"(it) } 70 | library.watchosArm32?.let { "watchosArm32$configuration"(it) } 71 | library.watchosArm64?.let { "watchosArm64$configuration"(it) } 72 | library.watchosSimulatorArm64?.let { "watchosSimulatorArm64$configuration"(it) } 73 | } 74 | 75 | fun DependencyHandlerScope.mppLibrary(library: MultiPlatformLibrary) { 76 | mppLibrary(configuration = "MainApi", library = library) 77 | } 78 | 79 | fun DependencyHandlerScope.mppTestLibrary( 80 | library: MultiPlatformLibrary 81 | ) { 82 | mppLibrary(configuration = "TestApi", library = library) 83 | } 84 | 85 | fun String.defaultMPL( 86 | android: Boolean = false, 87 | ios: Boolean = false, 88 | macos: Boolean = false, 89 | tvos: Boolean = false, 90 | watchos: Boolean = false 91 | ): MultiPlatformLibrary { 92 | return MultiPlatformLibrary( 93 | android = if (android) commonToPlatformArtifact(this, "android") else null, 94 | common = this, 95 | iosX64 = if (ios) commonToPlatformArtifact(this, "iosx64") else null, 96 | iosArm32 = if (ios) commonToPlatformArtifact(this, "iosarm32") else null, 97 | iosArm64 = if (ios) commonToPlatformArtifact(this, "iosarm64") else null, 98 | iosSimulatorArm64 = if (ios) commonToPlatformArtifact(this, "iossimulatorarm64") else null, 99 | macosX64 = if (macos) commonToPlatformArtifact(this, "macosx64") else null, 100 | macosArm64 = if (macos) commonToPlatformArtifact(this, "macosarm64") else null, 101 | tvosX64 = if (tvos) commonToPlatformArtifact(this, "tvosx64") else null, 102 | tvosArm64 = if (tvos) commonToPlatformArtifact(this, "tvosarm64") else null, 103 | tvosSimulatorArm64 = if (ios) commonToPlatformArtifact(this, "tvossimulatorarm64") else null, 104 | watchosArm32 = if (watchos) commonToPlatformArtifact(this, "watchosarm32") else null, 105 | watchosArm64 = if (watchos) commonToPlatformArtifact(this, "watchosarm64") else null, 106 | watchosSimulatorArm64 = if (ios) commonToPlatformArtifact(this, "watchossimulatorarm64") else null, 107 | watchosX86 = if (watchos) commonToPlatformArtifact(this, "watchosx86") else null, 108 | watchosX64 = if (watchos) commonToPlatformArtifact(this, "watchosx64") else null 109 | ) 110 | } 111 | 112 | private fun commonToPlatformArtifact(common: String, platform: String): String { 113 | return common.replace(Regex("(.*):(.*):(.*)"), "$1:$2-$platform:$3") 114 | } 115 | -------------------------------------------------------------------------------- /src/main/kotlin/MultiPlatformModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import org.gradle.api.Project 6 | import org.gradle.kotlin.dsl.DependencyHandlerScope 7 | import org.gradle.kotlin.dsl.project 8 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 9 | 10 | data class MultiPlatformModule( 11 | val name: String, 12 | val exported: Boolean = false 13 | ) : KotlinNativeExportable { 14 | 15 | override fun export(project: Project, framework: Framework) { 16 | if (!exported) return 17 | 18 | framework.export(project.project(name)) 19 | } 20 | } 21 | 22 | fun DependencyHandlerScope.mppModule(configuration: String, module: MultiPlatformModule) { 23 | val name = module.name 24 | "common$configuration"(dependencies.project(path = name)) 25 | } 26 | 27 | fun DependencyHandlerScope.mppModule(module: MultiPlatformModule) { 28 | mppModule(configuration = "MainApi", module = module) 29 | } 30 | 31 | fun DependencyHandlerScope.mppTestModule(module: MultiPlatformModule) { 32 | mppModule(configuration = "TestApi", module = module) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/AndroidManifestPlugin.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.gradle 6 | 7 | import com.android.build.gradle.LibraryExtension 8 | import com.android.build.gradle.api.AndroidSourceSet 9 | import org.gradle.api.Plugin 10 | import org.gradle.api.Project 11 | 12 | class AndroidManifestPlugin : Plugin { 13 | override fun apply(target: Project) { 14 | target.plugins.withId("com.android.library") { 15 | val androidExtension: LibraryExtension = target.extensions.findByType(LibraryExtension::class.java)!! 16 | val mainSourceSet: AndroidSourceSet = androidExtension.sourceSets.getByName("main") 17 | val newManifestPath = "src/androidMain/AndroidManifest.xml" 18 | mainSourceSet.manifest.srcFile(newManifestPath) 19 | 20 | target.logger.info("set new android manifest path $newManifestPath") 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/AndroidSourcesPlugin.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.gradle 6 | 7 | import com.android.build.gradle.LibraryExtension 8 | import org.gradle.api.Plugin 9 | import org.gradle.api.Project 10 | 11 | class AndroidSourcesPlugin : Plugin { 12 | override fun apply(target: Project) { 13 | target.plugins.withId("com.android.library") { 14 | val androidExtension = target.extensions.findByType(LibraryExtension::class.java)!! 15 | 16 | androidExtension.sourceSets.configureEach { 17 | val capitalizedName = name.capitalize() 18 | val newRoot = "src/android$capitalizedName" 19 | setRoot(newRoot) 20 | 21 | target.logger.info("set new root for $name android source set - $newRoot") 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/AppleFrameworkPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import com.android.build.gradle.internal.tasks.factory.dependsOn 8 | import org.gradle.api.Plugin 9 | import org.gradle.api.Project 10 | import org.gradle.api.tasks.Exec 11 | import org.gradle.api.tasks.TaskProvider 12 | import org.gradle.kotlin.dsl.withType 13 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 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 | import java.io.File 18 | 19 | open class AppleFrameworkPlugin : Plugin { 20 | override fun apply(target: Project) { 21 | val frameworkExtension = target.extensions.create("framework", FrameworkConfig::class.java) 22 | val kmpExtension = 23 | target.extensions.findByType(KotlinMultiplatformExtension::class.java) ?: return 24 | 25 | kmpExtension.targets.withType().matching(::targetFilter).configureEach { 26 | binaries { 27 | framework(frameworkExtension.name) { 28 | configureFrameworkExports(this, frameworkExtension) 29 | configureSyncFrameworkTasks(this) 30 | } 31 | } 32 | } 33 | } 34 | 35 | protected open fun targetFilter(target: KotlinNativeTarget): Boolean { 36 | return target.konanTarget.family.isAppleFamily 37 | } 38 | 39 | private fun configureFrameworkExports(framework: Framework, frameworkConfig: FrameworkConfig) { 40 | val project = framework.project 41 | project.afterEvaluate { 42 | frameworkConfig.exports.forEach { exportDeclaration -> 43 | project.logger.info("export $exportDeclaration") 44 | exportDeclaration.export(project, framework) 45 | } 46 | } 47 | } 48 | 49 | private fun configureSyncFrameworkTasks( 50 | framework: Framework 51 | ) { 52 | val linkTask: TaskProvider = framework.linkTaskProvider 53 | val syncTaskName: String = linkTask.name.replaceFirst("link", "sync") 54 | val project: Project = framework.project 55 | 56 | val outputDir = File(project.buildDir, "cocoapods/framework") 57 | val inputDir: File = framework.outputFile 58 | 59 | val syncTask: TaskProvider = project.tasks.register(syncTaskName, Exec::class.java) { 60 | group = "cocoapods" 61 | 62 | commandLine("cp", "-R", inputDir.absolutePath, outputDir.absolutePath) 63 | 64 | doFirst { 65 | if (outputDir.exists()) { 66 | outputDir.deleteRecursively() 67 | } 68 | outputDir.mkdirs() 69 | } 70 | } 71 | syncTask.dependsOn(linkTask) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/CocoaPodInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import java.io.File 8 | 9 | open class CocoaPodInfo( 10 | val name: String 11 | ) { 12 | var scheme: String = name 13 | var onlyLink: Boolean = false 14 | var precompiled: Boolean = false 15 | var frameworksPaths: List = emptyList() 16 | 17 | val module: String get() = name 18 | val capitalizedModule: String get() = module.capitalize() 19 | var extraModules: List = emptyList() 20 | var extraLinkerOpts: List = emptyList() 21 | 22 | private val onConfiguredBlocks = mutableListOf<() -> Unit>() 23 | private var configured = false 24 | 25 | override fun toString(): String { 26 | return "CocoaPodInfo(name = $name, module = $module, onlyLink = $onlyLink, " + 27 | "precompiled = $precompiled, extraModules = $extraModules, " + 28 | "extraLinkerOpts = $extraLinkerOpts)" 29 | } 30 | 31 | internal fun configured() { 32 | configured = true 33 | onConfiguredBlocks.forEach { it.invoke() } 34 | } 35 | 36 | internal fun doOnConfigured(block: () -> Unit) { 37 | if (configured) block() 38 | else onConfiguredBlocks.add(block) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/CocoapodsConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import org.gradle.api.NamedDomainObjectContainer 8 | import org.gradle.api.model.ObjectFactory 9 | import java.io.File 10 | import javax.inject.Inject 11 | 12 | 13 | open class CocoapodsConfig @Inject constructor(objectFactory: ObjectFactory) { 14 | lateinit var podsProject: File 15 | var buildConfiguration: String = "debug" 16 | 17 | internal val cocoapods: NamedDomainObjectContainer = 18 | objectFactory.domainObjectContainer(CocoaPodInfo::class.java) 19 | 20 | fun pod(name: String, onlyLink: Boolean = false) { 21 | pod(scheme = name, module = name, onlyLink = onlyLink) 22 | } 23 | 24 | fun pod(scheme: String, module: String, onlyLink: Boolean = false) { 25 | cocoapods.create(module) { 26 | this.scheme = scheme 27 | this.onlyLink = onlyLink 28 | }.configured() 29 | } 30 | 31 | fun precompiledPod( 32 | scheme: String, 33 | module: String = scheme, 34 | extraModules: List? = null, 35 | extraLinkerOpts: List? = null, 36 | onlyLink: Boolean = false, 37 | frameworksPathsResolver: (File) -> List 38 | ) { 39 | if (!::podsProject.isInitialized) { 40 | throw IllegalStateException("podsProject property should be set before call precompiledPod") 41 | } 42 | cocoapods.create(module) { 43 | this.scheme = scheme 44 | this.precompiled = true 45 | this.onlyLink = onlyLink 46 | this.frameworksPaths = frameworksPathsResolver(podsProject.parentFile) 47 | if (extraModules != null) this.extraModules = extraModules 48 | if (extraLinkerOpts != null) this.extraLinkerOpts = extraLinkerOpts 49 | }.configured() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/CocoapodsPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import dev.icerock.gradle.tasks.CompileCocoaPod 8 | import org.gradle.api.Plugin 9 | import org.gradle.api.Project 10 | import org.gradle.api.Task 11 | import org.gradle.kotlin.dsl.withType 12 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 13 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 14 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 15 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 16 | import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest 17 | import org.jetbrains.kotlin.konan.target.KonanTarget 18 | import java.io.File 19 | 20 | class CocoapodsPlugin : Plugin { 21 | override fun apply(target: Project) { 22 | val cocoaPodsExtension = target.extensions.create("cocoaPods", CocoapodsConfig::class.java) 23 | if (target.hasProperty(PROPERTY_PODS_PROJECT)) { 24 | val path = target.property(PROPERTY_PODS_PROJECT) as String 25 | cocoaPodsExtension.podsProject = File(target.rootDir, path) 26 | } 27 | if (target.hasProperty(PROPERTY_PODS_CONFIGURATION)) { 28 | val config = target.property(PROPERTY_PODS_CONFIGURATION) as String 29 | cocoaPodsExtension.buildConfiguration = config 30 | } 31 | 32 | target.plugins.withId("org.jetbrains.kotlin.multiplatform") { 33 | val kmpExtension = 34 | target.extensions.findByType(KotlinMultiplatformExtension::class.java)!! 35 | 36 | kmpExtension.targets 37 | .withType() 38 | .configureEach { 39 | configureCocoaPodsDependencies( 40 | cocoaPodsExtension = cocoaPodsExtension, 41 | kotlinNativeTarget = this, 42 | target = target 43 | ) 44 | } 45 | } 46 | } 47 | 48 | private fun configureCocoaPodsDependencies( 49 | cocoaPodsExtension: CocoapodsConfig, 50 | kotlinNativeTarget: KotlinNativeTarget, 51 | target: Project 52 | ) { 53 | cocoaPodsExtension.cocoapods.configureEach { 54 | doOnConfigured { 55 | if (!precompiled) { 56 | configureCocoaPod( 57 | target = kotlinNativeTarget, 58 | project = target, 59 | pod = this, 60 | cocoaPodsExtension = cocoaPodsExtension 61 | ) 62 | 63 | } 64 | 65 | if (!onlyLink) { 66 | configureCInterop( 67 | target = kotlinNativeTarget, 68 | pod = this, 69 | project = target 70 | ) 71 | } else if (precompiled) { 72 | configurePrecompiledLink( 73 | target = kotlinNativeTarget, 74 | pod = this 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | 81 | private fun configureCocoaPod( 82 | target: KotlinNativeTarget, 83 | project: Project, 84 | pod: CocoaPodInfo, 85 | cocoaPodsExtension: CocoapodsConfig 86 | ) { 87 | project.logger.info("configure cocoaPod $pod in $target of $project") 88 | 89 | val buildTask: CompileCocoaPod = configurePodCompilation( 90 | kotlinNativeTarget = target, 91 | pod = pod, 92 | project = project, 93 | cocoaPodsExtension = cocoaPodsExtension 94 | ) 95 | val frameworksDir = buildTask.frameworksDir 96 | 97 | target.binaries 98 | .configureEach { 99 | linkerOpts("-F${frameworksDir.absolutePath}") 100 | 101 | linkTask.dependsOn(buildTask) 102 | } 103 | 104 | project.tasks 105 | .withType(KotlinNativeTest::class) 106 | .matching { it.targetName == target.name } 107 | .configureEach { 108 | environment("SIMCTL_CHILD_DYLD_FRAMEWORK_PATH", frameworksDir.absolutePath) 109 | } 110 | } 111 | 112 | private fun configurePodCompilation( 113 | kotlinNativeTarget: KotlinNativeTarget, 114 | pod: CocoaPodInfo, 115 | project: Project, 116 | cocoaPodsExtension: CocoapodsConfig 117 | ): CompileCocoaPod { 118 | project.logger.info("configure compilation pod $pod in $kotlinNativeTarget of $project") 119 | 120 | val (sdk, arch) = when (kotlinNativeTarget.konanTarget) { 121 | KonanTarget.IOS_ARM64 -> "iphoneos" to "arm64" 122 | KonanTarget.IOS_X64 -> "iphonesimulator" to "x86_64" 123 | KonanTarget.IOS_SIMULATOR_ARM64 -> "iphonesimulator" to "arm64" 124 | else -> throw IllegalArgumentException("${kotlinNativeTarget.konanTarget} is unsupported") 125 | } 126 | val taskName = generateCompileCocoaPodTaskName(kotlinNativeTarget, pod) 127 | val taskProject = project.rootProject 128 | 129 | val existTask = taskProject.tasks.findByName(taskName) 130 | return if (existTask != null) { 131 | existTask as CompileCocoaPod 132 | } else { 133 | project.rootProject.tasks.create(taskName, CompileCocoaPod::class.java) { 134 | podInfo = pod 135 | compileSdk = sdk 136 | compileArch = arch 137 | config = cocoaPodsExtension 138 | } 139 | } 140 | } 141 | 142 | private fun generateCompileCocoaPodTaskName( 143 | kotlinNativeTarget: KotlinNativeTarget, 144 | pod: CocoaPodInfo 145 | ): String { 146 | val (sdk, arch) = when (kotlinNativeTarget.konanTarget) { 147 | KonanTarget.IOS_ARM64 -> "iphoneos" to "arm64" 148 | KonanTarget.IOS_X64 -> "iphonesimulator" to "x86_64" 149 | KonanTarget.IOS_SIMULATOR_ARM64 -> "iphonesimulator" to "arm64" 150 | else -> throw IllegalArgumentException("${kotlinNativeTarget.konanTarget} is unsupported") 151 | } 152 | val capitalizedPodName = pod.capitalizedModule 153 | val capitalizedSdk = sdk.capitalize() 154 | val capitalizedArch = arch.capitalize() 155 | return "cocoapodBuild$capitalizedPodName$capitalizedSdk$capitalizedArch" 156 | } 157 | 158 | private fun configureCInterop( 159 | target: KotlinNativeTarget, 160 | pod: CocoaPodInfo, 161 | project: Project 162 | ) { 163 | project.logger.info("configure cInterop for pod $pod in $target of $project") 164 | 165 | if (pod.precompiled) { 166 | createCInteropTask( 167 | project = project, 168 | kotlinNativeTarget = target, 169 | pod = pod, 170 | frameworksPaths = pod.frameworksPaths 171 | ) 172 | } else { 173 | val compileName = generateCompileCocoaPodTaskName(target, pod) 174 | project.rootProject.tasks.matching { it.name == compileName }.configureEach { 175 | val compileCocoaPod = this as CompileCocoaPod 176 | val cInteropTask = createCInteropTask( 177 | project = project, 178 | kotlinNativeTarget = target, 179 | pod = pod, 180 | frameworksPaths = listOf(compileCocoaPod.frameworksDir) 181 | ) 182 | cInteropTask.dependsOn(compileCocoaPod) 183 | } 184 | } 185 | } 186 | 187 | private fun createCInteropTask( 188 | project: Project, 189 | kotlinNativeTarget: KotlinNativeTarget, 190 | pod: CocoaPodInfo, 191 | frameworksPaths: List 192 | ): Task { 193 | val extraModulesLine = pod.extraModules.joinToString(separator = " ") 194 | val extraLinkerOptsLine = pod.extraLinkerOpts.joinToString(separator = " ") 195 | 196 | val defFile = File(project.buildDir, "cocoapods/def/${pod.module}.def") 197 | defFile.parentFile.mkdirs() 198 | defFile.writeText( 199 | """ 200 | language = Objective-C 201 | package = cocoapods.${pod.module} 202 | modules = ${pod.module} $extraModulesLine 203 | linkerOpts = -framework ${pod.module} $extraLinkerOptsLine 204 | """.trimIndent() 205 | ) 206 | 207 | val frameworksOpts = frameworksPaths 208 | .map { it.absolutePath } 209 | .map { "-F$it" } 210 | val compilation = 211 | kotlinNativeTarget.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) 212 | val capitalizedPodName = pod.capitalizedModule 213 | val cInteropSettings = compilation.cinterops.create("cocoapod$capitalizedPodName") { 214 | defFile(defFile) 215 | compilerOpts(frameworksOpts) 216 | } 217 | return project.tasks.getByName(cInteropSettings.interopProcessingTaskName) 218 | } 219 | 220 | private fun configurePrecompiledLink( 221 | target: KotlinNativeTarget, 222 | pod: CocoaPodInfo 223 | ) { 224 | target.binaries 225 | .matching { it is Framework } 226 | .configureEach { 227 | val framework = this as Framework 228 | 229 | val frameworks = pod.frameworksPaths 230 | .map { it.path } 231 | .map { "-F$it" } 232 | 233 | framework.linkerOpts(frameworks) 234 | } 235 | } 236 | 237 | private companion object { 238 | const val PROPERTY_PODS_PROJECT = "mobile.multiplatform.podsProject" 239 | const val PROPERTY_PODS_CONFIGURATION = "mobile.multiplatform.podsConfiguration" 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/FrameworkConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import KotlinNativeExportable 8 | import org.gradle.api.Project 9 | import org.gradle.api.artifacts.MinimalExternalModuleDependency 10 | import org.gradle.api.artifacts.ProjectDependency 11 | import org.gradle.api.provider.Provider 12 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 13 | import org.jetbrains.kotlin.konan.target.Architecture 14 | 15 | open class FrameworkConfig { 16 | var name: String = "MultiPlatformLibrary" 17 | 18 | internal val exports = mutableListOf() 19 | 20 | @Deprecated("use common artifact only") 21 | fun export(arm64Dependency: String, x64Dependency: String) { 22 | ExportDeclaration.ExternalExport( 23 | arm64 = arm64Dependency, 24 | x64 = x64Dependency 25 | ).let { exports.add(it) } 26 | } 27 | 28 | fun export(project: Project) { 29 | ExportDeclaration.ProjectExport(project).let { exports.add(it) } 30 | } 31 | 32 | fun export(kotlinNativeExportable: KotlinNativeExportable) { 33 | ExportDeclaration.Exportable(kotlinNativeExportable).let { exports.add(it) } 34 | } 35 | 36 | fun export(artifact: String) { 37 | ExportDeclaration.ArtifactStringExport(artifact).let { exports.add(it) } 38 | } 39 | 40 | fun export(provider: Provider) { 41 | ExportDeclaration.VersionCatalogExport(provider.get()).let { exports.add(it) } 42 | } 43 | 44 | fun export(project: ProjectDependency) { 45 | ExportDeclaration.ProjectExport(project.dependencyProject).let { exports.add(it) } 46 | } 47 | 48 | internal sealed class ExportDeclaration { 49 | data class ExternalExport( 50 | val arm64: String, 51 | val x64: String 52 | ) : ExportDeclaration() { 53 | override fun export(project: Project, framework: Framework) { 54 | val architecture = framework.target.konanTarget.architecture 55 | when (architecture) { 56 | Architecture.ARM64 -> framework.export(arm64) 57 | Architecture.X64 -> framework.export(x64) 58 | else -> throw IllegalArgumentException("unsupported architecture ($architecture) for export declaration") 59 | } 60 | } 61 | } 62 | 63 | data class ProjectExport( 64 | val project: Project 65 | ) : ExportDeclaration() { 66 | override fun export(project: Project, framework: Framework) { 67 | framework.export(this.project) 68 | } 69 | } 70 | 71 | data class Exportable( 72 | val kotlinNativeExportable: KotlinNativeExportable 73 | ) : ExportDeclaration() { 74 | override fun export(project: Project, framework: Framework) { 75 | kotlinNativeExportable.export(project, framework) 76 | } 77 | } 78 | 79 | data class ArtifactStringExport( 80 | val artifact: String 81 | ) : ExportDeclaration() { 82 | override fun export(project: Project, framework: Framework) { 83 | framework.export(this.artifact) 84 | } 85 | } 86 | 87 | data class VersionCatalogExport( 88 | val externalModuleDependency: MinimalExternalModuleDependency 89 | ) : ExportDeclaration() { 90 | override fun export(project: Project, framework: Framework) { 91 | framework.export(externalModuleDependency) 92 | } 93 | } 94 | 95 | abstract fun export(project: Project, framework: Framework) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/IosFrameworkPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 8 | import org.jetbrains.kotlin.konan.target.Family 9 | 10 | class IosFrameworkPlugin : AppleFrameworkPlugin() { 11 | override fun targetFilter(target: KotlinNativeTarget): Boolean { 12 | return target.konanTarget.family == Family.IOS 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/LogOutputStream.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.gradle 6 | 7 | import org.gradle.api.logging.LogLevel 8 | import org.gradle.api.logging.Logger 9 | import java.io.OutputStream 10 | 11 | internal class LogOutputStream( 12 | private val logger: Logger, 13 | private val level: LogLevel 14 | ) : OutputStream() { 15 | private var mem: StringBuffer = StringBuffer() 16 | 17 | override fun write(byte: Int) { 18 | if (byte.toChar() == '\n') { 19 | flush() 20 | return 21 | } 22 | 23 | mem = mem.append(byte.toChar()) 24 | } 25 | 26 | override fun flush() { 27 | logger.log(level, mem.toString()) 28 | mem = StringBuffer() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/MobileMultiPlatformPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 10 | 11 | class MobileMultiPlatformPlugin : Plugin { 12 | private val cocoapodsPlugin = CocoapodsPlugin() 13 | private val mobileTargetsPlugin = MobileTargetsPlugin() 14 | 15 | override fun apply(target: Project) { 16 | // backward compatibility apply 17 | cocoapodsPlugin.apply(target) 18 | mobileTargetsPlugin.apply(target) 19 | 20 | target.plugins.withId("org.jetbrains.kotlin.multiplatform") { 21 | val kmpExtension = 22 | target.extensions.findByType(KotlinMultiplatformExtension::class.java)!! 23 | 24 | kmpExtension.android { 25 | publishLibraryVariants("release", "debug") 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/MobileTargetsPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle 6 | 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import org.gradle.kotlin.dsl.get 10 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 11 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 12 | 13 | class MobileTargetsPlugin : Plugin { 14 | private val androidManifestPlugin = AndroidManifestPlugin() 15 | private val androidSourcesPlugin = AndroidSourcesPlugin() 16 | 17 | override fun apply(target: Project) { 18 | // backward compatibility apply 19 | androidManifestPlugin.apply(target) 20 | androidSourcesPlugin.apply(target) 21 | 22 | target.plugins.withId("org.jetbrains.kotlin.multiplatform") { 23 | val kmpExtension = 24 | target.extensions.findByType(KotlinMultiplatformExtension::class.java)!! 25 | 26 | setupMultiplatformTargets(kmpExtension, target) 27 | } 28 | } 29 | 30 | private fun setupMultiplatformTargets( 31 | kmpExtension: KotlinMultiplatformExtension, 32 | project: Project 33 | ) { 34 | kmpExtension.apply { 35 | android() 36 | val useShortcut = project.boolProperty(PROPERTY_USE_IOS_SHORTCUT) ?: true 37 | val useIosSimulatorArm64 = 38 | project.boolProperty(PROPERTY_WITHOUT_IOS_SIMULATOR_ARM64) ?: true 39 | if (useShortcut) { 40 | ios() 41 | } else { 42 | iosArm64() 43 | iosX64() 44 | } 45 | if (useIosSimulatorArm64) { 46 | iosSimulatorArm64() 47 | if (useShortcut) { 48 | val iosMain: KotlinSourceSet = sourceSets["iosMain"] 49 | val iosSimulatorArm64Main: KotlinSourceSet = sourceSets["iosSimulatorArm64Main"] 50 | iosSimulatorArm64Main.dependsOn(iosMain) 51 | 52 | val iosTest: KotlinSourceSet = sourceSets["iosTest"] 53 | val iosSimulatorArm64Test: KotlinSourceSet = sourceSets["iosSimulatorArm64Test"] 54 | iosSimulatorArm64Test.dependsOn(iosTest) 55 | } 56 | } 57 | } 58 | } 59 | 60 | private fun Project.boolProperty(name: String): Boolean? { 61 | val valueString: String = project.findProperty(name) as? String ?: return null 62 | return valueString.toLowerCase() == "true" 63 | } 64 | 65 | private companion object { 66 | const val PROPERTY_USE_IOS_SHORTCUT = "mobile.multiplatform.useIosShortcut" 67 | const val PROPERTY_WITHOUT_IOS_SIMULATOR_ARM64 = 68 | "mobile.multiplatform.withoutIosSimulatorArm64" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/icerock/gradle/tasks/CompileCocoaPod.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.gradle.tasks 6 | 7 | import dev.icerock.gradle.CocoaPodInfo 8 | import dev.icerock.gradle.CocoapodsConfig 9 | import dev.icerock.gradle.LogOutputStream 10 | import org.gradle.api.DefaultTask 11 | import org.gradle.api.logging.LogLevel 12 | import org.gradle.api.tasks.Input 13 | import org.gradle.api.tasks.InputDirectory 14 | import org.gradle.api.tasks.Internal 15 | import org.gradle.api.tasks.OutputDirectory 16 | import org.gradle.api.tasks.TaskAction 17 | import java.io.File 18 | 19 | open class CompileCocoaPod : DefaultTask() { 20 | init { 21 | group = "cocoapods" 22 | } 23 | 24 | @get:Internal 25 | internal lateinit var config: CocoapodsConfig 26 | 27 | @get:InputDirectory 28 | val podsProject: File 29 | get() = config.podsProject 30 | 31 | @get:InputDirectory 32 | val podsDir: File 33 | get() = podsProject.parentFile 34 | 35 | @get:Input 36 | lateinit var compileSdk: String 37 | 38 | @get:Input 39 | lateinit var compileArch: String 40 | 41 | @get:Internal 42 | internal lateinit var podInfo: CocoaPodInfo 43 | 44 | @get:Input 45 | val scheme: String 46 | get() = podInfo.scheme 47 | 48 | @get:Input 49 | val configuration: String 50 | get() = config.buildConfiguration 51 | 52 | private val cocoapodsDir: File get() = File(project.buildDir, "cocoapods") 53 | private val outputDir: File get() = File(cocoapodsDir, compileArch) 54 | private val derivedData: File get() = File(cocoapodsDir, "DerivedData") 55 | 56 | @get:OutputDirectory 57 | val frameworksDir: File 58 | get() = File(outputDir, "UninstalledProducts/$compileSdk") 59 | 60 | @TaskAction 61 | fun compile() { 62 | val podsProjectPath = podsProject.absolutePath 63 | val outputPath = outputDir.absolutePath 64 | val derivedDataPath = derivedData.absolutePath 65 | val cmdLine = arrayOf( 66 | "xcodebuild", 67 | "-project", podsProjectPath, 68 | "-scheme", scheme, 69 | "-sdk", compileSdk, 70 | "-arch", compileArch, 71 | "-configuration", configuration, 72 | "-derivedDataPath", derivedDataPath, 73 | "SYMROOT=$outputPath", 74 | "DEPLOYMENT_LOCATION=YES", 75 | "SKIP_INSTALL=YES", 76 | "build" 77 | ) 78 | cmdLine.joinToString(separator = " ").also { 79 | project.logger.lifecycle("cocoapod build cmd: $it") 80 | } 81 | 82 | val result = project.exec { 83 | workingDir = podsProject 84 | commandLine = cmdLine.toList() 85 | standardOutput = LogOutputStream(project.logger, LogLevel.INFO) 86 | errorOutput = LogOutputStream(project.logger, LogLevel.ERROR) 87 | } 88 | project.logger.lifecycle("xcodebuild result is ${result.exitValue}") 89 | result.assertNormalExitValue() 90 | } 91 | } 92 | --------------------------------------------------------------------------------