Multiple Kotlin MPP native libraries with common linked dependencies - gradle

I have multiple Kotlin MPP projects that I want to compile and use as separate libraries in iOS and Android. The MPP projects have their own build.gradle files and are combined into a composite build structure, so one task builds them all. This works well for building the jars. The iOS frameworks are also built but not the way I'd like.
There is a dependency structure among the libraries, for example a core library that all of the others use and another one that references all the others, and some in-between. When compiling the libraries, they come out as fat frameworks containing all of their dependencies. Their types are named with a prefix according to their Kotlin project name which makes types from different libraries incompatible in the consuming app, even though the types are the same in Kotlin.
Here is an example: let's say we have App, Lib_A and Lib_B. In Kotlin common_main, Lib_A references lib_b.Clazz of Lib_B. In Android, the same lib_b.Clazz can be referenced both from Lib_B and the App and between Lib_A and Lib_B. However, in iOS, since namespaces do not exist, Clazz is called Lib_ALib_BClazz in Lib_A and LibBClazz in Lib_B. To the App, these are therefore different types and cannot be used together. Also, in the compiled iOS frameworks, all visible transitative dependencies are included in each framework.
Question: Is there some way in Gradle to tell the MPP plugin not to include all the dependencies in each compiled iOS framework, so I can provide them all from App instead and their types will be compatible?
Here is the basic build.gradle for each Kotlin MPP project, that is then included in the composite build:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
repositories {
mavenCentral()
mavenLocal()
jcenter()
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" }
}
apply plugin: 'maven-publish'
kotlin {
iosArm64 {
binaries.framework {
baseName = project.name
}
}
iosX64 {
binaries.framework {
baseName = project.name
}
}
sourceSets {
commonMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2'
implementation 'Lib_B' // example
implementation 'Lib_C' // example
}
}
iosArm64Main {
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.2'
}
}
iosX64Main {
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.2'
}
}
}
}
task runBuildIosArm64(type: GradleBuild) {
buildFile = 'ProjectName/build.gradle'
tasks = ['linkReleaseFrameworkIos']
startParameter.setProjectProperties([
'IOS_TARGET': 'iosArm64',
'XCODE_CONFIGURATION': 'RELEASE'
])
}
task runBuildIosX64(type: GradleBuild) {
buildFile = 'ProjectName/build.gradle'
tasks = ['linkReleaseFrameworkIos']
startParameter.setProjectProperties([
'IOS_TARGET': 'iosX64',
'XCODE_CONFIGURATION': 'RELEASE'
])
}
task cleanFrameworksFolder(type: Delete) {
delete 'ProjectName/build/xcode-frameworks'
}
task combineIosArchitectures(type: Exec) {
executable 'lipo'
args = [
'-create',
'-arch', 'arm64', 'ProjectName/build/xcode-frameworks/ProjectName_iosArm64.framework/ProjectName',
'-arch', 'x86_64', 'ProjectName/build/xcode-frameworks/ProjectName_iosX64.framework/ProjectName',
'-output', 'ProjectName/build/xcode-frameworks/ProjectName.framework/ProjectName',
]
}
combineIosArchitectures.dependsOn cleanFrameworksFolder
combineIosArchitectures.dependsOn runBuildIosArm64
combineIosArchitectures.dependsOn runBuildIosX64

Related

no build output compiled for common module of Kotlin Multiplatform project

I'm trying to figure out why dependent projects for my Kotlin MPP library don't see any provided modules in their common modules even though the targets (jvm, android) can see them.
Published via maven-publish.
The /build directory for the library contains nothing I can identify as an intermediate representation of my common modules, leading me to think that I need to explicitly tell Gradle to produce the files to be included as common in the published package.
As it is, the .aar and .jar files produced in the android and desktop (jvm) modules each look normal, but the published common module is empty.
I need that common module to be populated before I can code against it inside the common module of dependent projects.
Here is the relevant section of my build.gradle.kts. I omit the repository config as it appears to work.
I basically followed the instructions from kotlinlang.org.
I've looked at the maven-publish plugin configuration, the settings for the kotlin-multiplatformm plugin, and the configured project structure.
kotlin version is 1.6.10, unable to update due to Jetbrains Compose dependency.
plugins {
kotlin("multiplatform")
id("com.android.library")
id("maven-publish")
}
kotlin {
android {
publishLibraryVariants = listOf("release", "debug")
}
jvm("desktop") {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
val publicationsFromMainHost = listOf(jvm("desktop").name, "kotlinMultiplatform")
publishing {
publications {
matching { it.name in publicationsFromMainHost }.all {
val targetPublication = this#all
tasks.withType<AbstractPublishToMaven>()
.matching { it.publication == targetPublication }
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting {
dependencies {
implementation("androidx.startup:startup-runtime:1.1.1")
}
}
val androidTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
implementation("androidx.test:core:1.4.0")
implementation("androidx.test:runner:1.4.0")
implementation("androidx.test:rules:1.4.0")
implementation("org.robolectric:robolectric:4.6.1")
}
}
val desktopMain by getting
val desktopTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
}
}
}
}
The answer is to manually supply the Kotlin stdlib dependency, rather than relying on the gradle plugin to add it.
When there are only jvm-based builds present, commonMain will be built with platform type of jdk8 rather than common. By making an explicit dependency on stdlib-common, it will be coerced back to the common platform, and then the correct metadata will be created and published.
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
}
}

Gradle 7.0 Version Catalog for maven bom

I have published maven bom and imported it in top level build.gradle.kts as:
allProjects {
dependencies {
implementation(platform("com.example:some-dependencies:1.2.3"))
}
}
And then in libs.versions.toml:
[libraries]
some-bom = { group = "com.example", name="some-dependencies", version="1.2.3" }
When I change first code sample to:
allProjects {
dependencies {
implementation(platform(libs.some.bom))
}
}
I get:
Could not resolve: javax.xml.bind:jaxb-api
Could not resolve: org.springframework.boot:spring-boot-starter-test
...
Is there any way to use Gradle 7 version catalogs with boms?
In my case, it just worked. I'm working on Android project and my script is just like below:
//libs.versions.toml
[libraries]
deps_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:4.9.1"
deps_okhttp_lib = { module ="com.squareup.okhttp3:okhttp" }
deps_okhttp_logging_interceptor = { module= "com.squareup.okhttp3:logging-interceptor"}
//build.xml
dependencies {
implementation platform(libs.deps.okhttp.bom)
implementation libs.deps.okhttp.lib
implementation libs.deps.okhttp.logging.interceptor
}
In your example, you just added dependency for BOM. But as BOM is just an spec sheet which describes versions for each libraries, you need to add dependencies for specific libraries.

Use Gradle to generate Java classes from a Swagger definition into a standalone JAR

I am fairly new to both Gradle and Swagger code generator plugin for it (concretely the one that is linked from Swagger's website, i.e. https://github.com/thebignet/swagger-codegen-gradle-plugin), so I'm not sure whether my problem is with Gradle in general or with that particular plugin.
I've created a simple multi-module Spring Boot application (but the fact that I'm using Spring Boot or even Spring doesn't matter much). It's a console application; i.e. it doesn't start a webserver. In fact, it's actually a REST client consuming someone else's interface.
The application consists of four modules: spc-parent (which is just an envelope for the rest) containing spc-boot, spc-service, and spc-integration-model. Spc-boot contains just the starting point of the application, spc-service now contains a single Spring service, and spc-integration-model is meant to contain classes needed to consume the REST interface. The resulting structure will be much more complicated but I've tried to create a sort of a minimal example.
The problem lies within the spc-integration-model module. It consists of a single source file, petstore.json, and a build.gradle copied from https://github.com/thebignet/swagger-codegen-gradle-plugin (and only slightly modified). There are actually two problems (but they may have the same underlying cause).
When running gradle build (from spc-parent) for the very first time, it fails. Java sources are generated from petstore.json but they don't get compiled, which is why the service in spc-service doesn't see needed classes. However, running gradle build a second time fixes this (generated Java sources get compiled which makes it possible to compile spc-service, too).
The created JAR of spc-integration-model never contains anything besides Manifest.
My goal here is to persuade Gradle to compile the generated classes right away during the first build and also to put them into the JAR.
Now for some concrete Gradle tasks. The most interesting is spc-integration-model's build.gradle:
plugins {
id 'org.detoeuf.swagger-codegen' version '1.7.4'
id 'java'
}
apply plugin: 'org.detoeuf.swagger-codegen'
repositories {
mavenCentral()
jcenter()
}
swagger {
inputSpec = 'http://petstore.swagger.io/v2/swagger.json'
outputDir = file('build/swagger')
lang = 'java'
additionalProperties = [
'apiPackage' : 'ondra.spc.integration.client.api',
'dateLibrary' : 'java8',
'hideGenerationTimestamp': 'true',
'invokerPackage' : 'ondra.spc.integration.client',
'library' : 'resttemplate',
'modelNameSuffix' : 'Dto',
'modelPackage' : 'ondra.spc.integration.client.model'
]
importMappings = [
'Dog': 'io.swagger.petstore.client.model.Dog'
]
}
sourceSets {
swagger {
java {
srcDir file("${project.buildDir.path}/swagger/src/main/java")
}
}
}
classes.dependsOn('swagger')
ext {
spring_boot_version = springBootVersion
jackson_version = jacksonVersion
junit_version = jUnitVersion
swagger_annotations_version = swaggerAnnotationsVersion
swagger_codegen_version = swaggerCodegenVersion
}
dependencies {
swaggerCompile "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
swaggerCompile "io.swagger:swagger-annotations:$swagger_annotations_version"
compile sourceSets.swagger.output
compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
compile "io.swagger:swagger-codegen:$swagger_codegen_version"
testCompile "junit:junit:$junit_version"
}
(Now that I'm re-reading my question I see that the local version of petstore.json is actually not used and an online version is used instead but let's leave that aside.)
The rest should be quite straightforward. spc-service:
dependencies {
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile project (":spc-integration-model")
}
spc-boot:
dependencies {
compile "org.springframework.boot:spring-boot:$springBootVersion"
compile "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion"
compile "org.springframework:spring-web:$springVersion"
runtime "org.hibernate.validator:hibernate-validator:$hibernateVersion"
runtime "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile project (":spc-service")
testCompile("org.junit.jupiter:junit-jupiter-api:$jUnitVersion")
testRuntime("org.junit.jupiter:junit-jupiter-engine:$jUnitVersion")
}
test {
useJUnitPlatform()
}
spc-parent:
subprojects {
apply plugin: 'java'
group 'ondra'
version '1.0-SNAPSHOT'
buildscript {
ext {
hibernateVersion = '6.0.9.Final'
jacksonVersion = '2.9.4'
springBootVersion = '2.0.0.RELEASE'
springVersion = '5.0.5.RELEASE'
swaggerAnnotationsVersion = '1.5.16'
swaggerCodegenVersion = '2.2.3'
jUnitVersion = '5.1.1'
}
repositories {
mavenCentral()
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
}
And spc-parent's settings.gradle:
rootProject.name = 'spc-parent'
include 'spc-boot'
include 'spc-service'
include 'spc-integration-model'
I've also put the whole application into a single archive: https://drive.google.com/open?id=1cOYIcaxnhik548w0wEGswgD2g4udATdD

Managing Shared Protocol Buffer library and using Gradle to Compile

I'd like to have 3 java applications (a backend, a front end, and an Android app) using protocol buffers (gRPC) to communicate. So I would like the 3 apps all to be able to have access to a shared protobuf repo (Github) where I manage the .proto files. I am new to using Gradle and protobufs, so I'm not sure what the proper way to manage this is, and any help or guidance would be appreciated. Can I have each Gradle project declare my github protobuf repo as a dependency, and then pull it down and compile it when I build the project? I would assume this way would be a good way to do it, rather than storing the compiled protobuf classes, since the Android app might need a different "Java-lite" version of the protobufs? I am using the google/protobuf-gradle-plugin to compile the .proto files, and see documentation for compile from local files, or pulling in projects that have precompiled .proto files, but no documentation for pulling in remote .proto files. Am I on the right track?
In what form is your remote .proto file/repo? If it is just a url, then you can use Download task:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
}
}
plugins {
id "de.undercouch.download" version "3.2.0"
}
group 'testtest'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'com.google.protobuf'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
task downloadFile << {
download {
src 'https://raw.githubusercontent.com/grpc/grpc-java/master/compiler/src/test/proto/test.proto'
dest "$projectDir/src/main/proto/test.proto"
overwrite true
}
}
build.dependsOn downloadFile
dependencies {
compile "io.grpc:grpc-protobuf-lite:1.5.0"
compile "io.grpc:grpc-stub:1.5.0"
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.3.0'
}
plugins {
javalite {
artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
}
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.5.0"
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.plugins {
javalite {}
grpc {
option 'lite'
}
}
}
}
}

avro gradle plugin sample usage

I am trying to use the avro-gradle-plugin on github, but have not gotten any luck getting it to work. Does anyone have any sample code on how they get it to work?
I figured out how to do it myself. The following is a snippet that I would like to share for people who might run into the same issues as I did:
apply plugin: 'java'
apply plugin: 'avro-gradle-plugin'
sourceCompatibility = "1.6"
targetCompatibility = "1.6"
buildscript {
repositories {
maven {
// your maven repo information here
}
}
dependencies {
classpath 'org.apache.maven:maven-artifact:2.2.1'
classpath 'org.apache.avro:avro-compiler:1.7.1'
classpath 'org.apache.avro.gradle:avro-gradle-plugin:1.7.1'
}
}
compileAvro.source = 'src/main/avro'
compileAvro.destinationDir = file("$buildDir/generated-sources/avro")
sourceSets {
main {
java {
srcDir compileAvro.destinationDir
}
}
}
dependencies {
compileAvro
}
I found "com.commercehub.gradle.plugin.avro" gradle plugin to work better.
use the folllowing:
// Gradle 2.1 and later
plugins {
id "com.commercehub.gradle.plugin.avro" version "VERSION"
}
// Earlier versions of Gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.commercehub.gradle.plugin:gradle-avro-plugin:VERSION"
}
}
apply plugin: "com.commercehub.gradle.plugin.avro"
more details at https://github.com/commercehub-oss/gradle-avro-plugin
When evaluating a plugin the following questions needs to be asked:
Are generated files included into source jar?
Is plugin fast? Good plugin use avro tools api instead of forking VM for every file. For large amount of files creating VM for every file can take 10min to compile.
Do you need intermediate avsc files?
Is build incremental (i.e. do not regenerate all files unless one of the sources changed)?
Is plugin flexible enough to give access to generated schema files, so further actions, such as registration schema in schema repository can be made?
It is easy enough to implement without any plugin if you are not happy with plugin or need more flexibility.
//
// define source and destination
//
def avdlFiles = fileTree('src/Schemas').include('**/*.avdl')
// Do NOT generate into $buildDir, because IntelliJ will ignore files in
// this location and will show errors in source code
def generatedJavaDir = "generated/avro/java"
sourceSets.main.java.srcDir generatedJavaDir
//
// Make avro-tools available to the build script
//
buildscript {
dependencies {
classpath group:'org.apache.avro', name:'avro-tools' ,version: avro_version
}
}
//
// Define task's input and output, compile idl to schema and schema to java
//
task buildAvroDtos(){
group = "build"
inputs.files avdlFiles
outputs.dir generatedJavaDir
doLast{
avdlFiles.each { avdlFile ->
def parser = new org.apache.avro.compiler.idl.Idl(avdlFile)
parser.CompilationUnit().getTypes().each { schema ->
def compiler = new org.apache.avro.compiler.specific.SpecificCompiler(schema)
compiler.compileToDestination(avdlFile, new File(generatedJavaDir))
}
}
}
}
//
// Publish source jar, including generated files
//
task sourceJar(type: Jar, dependsOn: buildAvroDtos) {
from sourceSets.main.allSource
// Package schemas into source jar
into("Schemas") { from avdlFiles }
}
// Clean "generated" folder upon "clean" task
clean {
delete('generated')
}
Configuration for avro with gradle as build tool need to add along with applying java plugin.
below changes in settings.gradle
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
below changes in build.gradle
plugins {
id "com.github.davidmc24.gradle.plugin.avro" version "1.3.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.apache.avro:avro:1.11.0"
}
generateAvroJava {
source("${projectDir}/src/main/resources/avro")//sourcepath avrofile
}
if you want to generate setter methods too add this task too in build.gradle
avro {
createSetters = true
}
link for reference

Resources