I'm trying to make swagger codegen work in a project built with gradle (kotlin).
My reference is this example here : https://github.com/int128/gradle-swagger-generator-plugin which is made in Gradle groovy version.
Now the build.gradle.kts is the following:
repositories {
jcenter()
}
plugins {
java
id("org.springframework.boot") version "2.1.2.RELEASE"
id("io.spring.dependency-management") version "1.0.6.RELEASE"
id("org.hidetake.swagger.generator") version "2.16.0"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation ("io.swagger:swagger-annotations:1.5.21")
swaggerCodeGen("io.swagger:swagger-codegen-cli:2.3.1")
// Use JUnit test framework
testImplementation ("junit:junit:4.12")
}
swaggerSources {
petstore {
inputFile = file('petstore.yaml')
code {
language = 'spring'
}
}
}
But IntelliJ does not like lines talking about swagger:
I am a newbie in gradle so I don't understand what I am supposed to do. Is swaggerCodeGen supposed to be a function? Where does this function supposed to be imported? Where swaggerSources supposed to imported?
import org.hidetake.gradle.swagger.generator.GenerateSwaggerCode
// plugins, repositories are same, but note import above ^^^
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation ("io.swagger:swagger-annotations:1.5.21")
"swaggerCodegen"("io.swagger:swagger-codegen-cli:2.3.1") // 1
// Use JUnit test framework
testImplementation ("junit:junit:4.12")
}
swaggerSources {
create("petstore").apply { // 2
setInputFile(file("petstore.yaml")) // 3
code(closureOf<GenerateSwaggerCode> { // 4
language = "spring"
})
}
}
1 - dynamically resolved configuration in Kotlin looks like this (dynamically from Groovy, so there is problematically to use it on compile time, extension invoke operator on String is our saviour);
2 - swaggerSources returns you NamedDomainObjectContainer<SwaggerSource>, so to add new container we invoke create with it's name as parameter;
3 - Kotlin does not as flexible as Groovy, so calling setter instead of setting field;
4 - Groovy's closure is far from functional interface, so we specify generic type as in plugin's sources Closure is not parametrised.
You can use this openapi-generator plugin task to generate the swagger code as well.
It does the same thing as the swagger codegen plugin. Use it in your build.gradle.kts like:
plugins {
id("org.openapi.generator") version "5.1.1"
}
openApiGenerate {
generatorName.set("spring")
inputSpec.set("$rootDir/src/main/resources/petstore.yaml")
outputDir.set("$buildDir/generated/")
}
dependencies {
//Spring boot dependency
implementation("org.springframework.boot:spring-boot-starter-web")
// For swagger generated code and annotations
implementation("io.springfox:springfox-boot-starter:3.0.0")
implementation("javax.validation:validation-api:2.0.0.Final")
}
This could be used with a kotlin or java project, then you need to add the generated classes to your sourceSet by doing:
configure<SourceSetContainer> {
named("main") {
java.srcDir("$buildDir/generated/src/main/java")
}
}
The last step is make sure you generate the swagger files before compiling, for Kotlin, add this in your compile task:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependsOn("openApiGenerate")
kotlinOptions.jvmTarget = "11"
}
You can check the generator's properties to adjust the configuration of the generated files.
Related
I'd like to write some of my code in Kotlin such that it can be used by both a JVM-based backend and a JS-based frontend.
To do the former (with the new IR complier), the classes have to be annotated with #JsExport. However, if I then try to use build the same file for a JVM, it breaks because that annotation is not recognized.
(These are separate projects with independent gradle configs but linking to the same Kotlin source tree. It's not a single "multiplatform" build.)
How can I export Kotlin/JS while still being compatible with Kotlin/Java?
To achieve that seperation and reusability, you need three gradle submodules that would depend on each other
The 'shared' module. The one with the common code
The 'backend' module. Purely kotlin/jvm module
The 'frontend' module. A purely kotlin/js module
Your gradle project structure should look something like this
project-root
- shared
- src/commonMain/kotlin
- build.gradle.kts
- backend
- src/main/kotlin
- build.gradle.kts
- frontend
- src/main/kotlin
- build.gradle.kts
- settings.gradle.kts
shared/build.gradle.kts should look like this
plugins {
kotlin("multiplatform")
}
and then backend/build.gradle.kts
plugins {
kotlin("jvm")
}
kotlin {
sourceSets {
val main by getting {
dependencies {
implementation(projects(":shared"))
}
}
}
}
and then backend/build.gradle.kts
plugins {
kotlin("js")
}
kotlin {
sourceSets {
val main by getting {
dependencies {
implementation(projects(":shared"))
}
}
}
}
and then settings.gradle.kts
include(":shared",":backend",":frontend")
With this sort of arrangement, You can write your shared code inside shared/src/commonMain/kotlin and environment specific code on their respective backend or frontend submodules
NOTE: The gradle configs above have been minimised to narrow down the explanation
#JsExport annotation works fine provided you are in a common source set.
I've done a quick example at: https://github.com/SimonCJacobs/JsExportExample
Run the Gradle tasks "jsRun" or "jvmRun" to see a #JsExport used in both platforms with the JS IR compiler.
I'm setting up a multi-module Gradle project based on Kotlin for the JVM. Since the root project does not contain any code, the Kotlin plugin should only be applied to subprojects.
build.gradle.kts (root project)
plugins {
kotlin("jvm") version "1.6.20" apply false
}
subprojects {
apply(plugin = "kotlin")
group = "com.example"
repositories {
mavenCentral()
}
dependencies {}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
Trying to set a toolchain causes the build to fail at the kotlin {...} extension:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public fun DependencyHandler.kotlin(module: String, version: String? = ...): Any defined in org.gradle.kotlin.dsl
public fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec defined in org.gradle.kotlin.dsl
It works fine if I copy the extension definition to each subproject build script, but why isn't it available in the main script?
This is one of my favourite things to fix in Gradle, and really shows off the flexibility that's possible (as well as demonstrating why Gradle can be complicated!)
First I'll give a bit of background info on the subprojects {} DSL, then I'll show how to fix your script, and finally I'll show the best way to share build logic with buildSrc convention plugins. (Even though it's last, I really recommend using buildSrc!)
Composition vs Inheritance
Using allprojects {} and subprojects {} is really common, I see it a lot. It's more similar to how Maven works, where all the configuration is defined in a 'parent' build file. However it's not recommended by Gradle.
[A], discouraged, way to share build logic between subproject is cross project configuration via the subprojects {} and allprojects {} DSL constructs.
Gradle Docs: Sharing Build Logic between Subprojects
(It's probably common because it's easy to understand - it makes Gradle work more like Maven, so each project inherits from one parent. But Gradle is designed for composition. Further reading: Composition over inheritance: Gradle vs Maven)
Quick fix: 'Unresolved reference'
The error you're seeing is basically because you haven't applied the Kotlin plugin.
plugins {
kotlin("jvm") version "1.6.20" apply false // <- Kotlin DSL won't be loaded
}
The kotlin { } configuration block is a very helpful extension function that is loaded when the Kotlin plugin is applied. Here's what it looks like:
/**
* Configures the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension] extension.
*/
fun org.gradle.api.Project.`kotlin`(configure: Action<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", configure)
// (note: this is generated code)
So if we don't have the extension function, we can just call configure directly, and thus configure the Kotlin extension.
subprojects {
// this is the traditional Gradle way of configuring extensions,
// and what the `kotlin { }` helper function will call.
configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension> {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
// without the Kotlin Gradle plugin, this helper function isn't available
// kotlin {
// jvmToolchain {
// check(this is JavaToolchainSpec)
// languageVersion.set(JavaLanguageVersion.of(11))
// }
// }
}
However, even though this works, using subprojects {} has problems. There's a better way...
buildSrc and Convention Plugins
buildSrc is, basically, a standalone Gradle project, the output of which we can use in the main project's build scripts. So we can write our own custom Gradle plugins, defining conventions, which we can selectively apply to any subproject in the 'main' build.
(This is the key difference between Gradle and Maven. In Gradle, a subproject can be configured by any number of plugins. In Maven, there's only one parent. Composition vs Inheritance!)
The Gradle docs have a full guide on setting up convention plugins, so only I'll briefly summarise the solution here.
1. Set up ./buildSrc
Create a directory named buildSrc in your project root.
Because buildSrc is a standalone project, create a ./buildSrc/build.gradle.kts and ./buildSrc/settings.gradle.kts files, like usual for a project.
In ./buildSrc/build.gradle.kts,
apply the kotlin-dsl plugin
add dependencies on Gradle plugins that you want to use anywhere in your project
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl` // this will create our Gradle convention plugins
// don't add the Kotlin JVM plugin
// kotlin("jvm") version embeddedKotlinVersion
// Why? It's a long story, but Gradle uses an embedded version of Kotlin,
// (which is provided by the `kotlin-dsl` plugin)
// which means importing an external version _might_ cause issues
// It's annoying but not important. The Kotlin plugin version below,
// in dependencies { }, will be used for building our 'main' project.
// https://github.com/gradle/gradle/issues/16345
}
val kotlinVersion = "1.6.20"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
Note that I've used the Maven repository coordinates for the Kotlin Gradle plugin, not the plugin ID!
You can also add other dependencies into ./buildSrc/build.gradle.kts if you like. If you wanted to parse JSON in a build script, then add a dependency on a JSON parser, like kotlinx-serialization.
2. Create a convention plugin
Create your Kotlin JVM convention that you can apply to any Kotlin JVM subproject.
// ./buildSrc/src/main/kotlin/my/project/convention/kotlin-jvm.gradle.kts
package my.project.convention
plugins {
kotlin("jvm") // don't include a version - that's provided by ./buildSrc/build.gradle.kts
}
dependencies {
// you can define default dependencies, if desired
// testImplementation(kotlin("test"))
}
kotlin {
jvmToolchain {
check(this is JavaToolchainSpec)
languageVersion.set(JavaLanguageVersion.of(11))
}
}
}
Don't forget to add the package declaration! I've forgotten it a few times, and it causes errors that are hard to figure out.
3. Applying the convention plugin
Just like how Gradle plugins have IDs, so do our convention plugins. It's the package name + the bit before .gradle.kts. So in our case the ID is my.project.convention.kotlin-jvm
We can apply this like a regular Gradle plugin...
// ./subprojects/my-project/build.gradle.kts
plugins {
id("my.project.convention.kotlin-jvm")
}
(Convention plugins can also import other convention plugins, using id("..."))
Also, since we're using Kotlin, there's an even nicer way. You know how there are included Gradle plugins, like java and java-library. We can import our convention plugins the same way!
// ./subprojects/my-project/build.gradle.kts
plugins {
// id("my.project.convention.kotlin-jvm")
my.project.convention.`kotlin-jvm` // this works just like id("...") does
}
Note the backticks around the plugin ID - they're needed because of the hyphen.
(caveat: this non-id("...") way doesn't work inside buildSrc, only in the main project)
Result
Now the root ./build.gradle.kts can be kept really clean and tidy - it only needs to define the group and version of the project.
Because we're using convention plugins and not blanket subprojects, each subproject can be specialised and only import convention plugins that it needs, without repetition.
Site note: sharing repositories between buildSrc and the main project
Usually you want to share repositories between buildSrc and the main project. Because Gradle plugins are not specifically for projects, we can write a plugin for anything, including settings.gradle.kts!
What I do is create a file with all the repositories I want to use...
// ./buildSrc/repositories.settings.gradle.kts
#Suppress("UnstableApiUsage") // centralised repository definitions are incubating
dependencyResolutionManagement {
repositories {
mavenCentral()
jitpack()
gradlePluginPortal()
}
pluginManagement {
repositories {
jitpack()
gradlePluginPortal()
mavenCentral()
}
}
}
fun RepositoryHandler.jitpack() {
maven("https://jitpack.io")
}
(the name, repositories.settings.gradle.kts, isn't important - but naming it *.settings.gradle.kts should mean IntelliJ provides suggestions, however this is bugged at the moment.)
I can then import this as a plugin in the other settings.gradle.kts files, just like how you were applying the Kotlin JVM plugin to subprojects.
// ./buildSrc/settings.gradle.kts
apply(from = "./repositories.settings.gradle.kts")
// ./settings.gradle.kts
apply(from = "./buildSrc/repositories.settings.gradle.kts")
I have a gradle kotlin project, and I'm generating a kotlin file from a Rust project, so it ends up in a totally different place with no gradle project structure, etc.
How do I import this file into my gradle project?
It has its own package but it's a completely standalone file. This is my gradle file:
rootProject.name = "my_project"
include("app")
It's a desktop project, NOT android.
My build.gradle.kts:
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
id("org.jetbrains.kotlin.jvm") version "1.5.31"
// Apply the application plugin to add support for building a CLI application in Java.
application
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// This dependency is used by the application.
implementation("com.google.guava:guava:30.1.1-jre")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
application {
// Define the main class for the application.
mainClass.set("my_project.ffi.AppKt")
}
Adding the following code to your build.gradle.kts should do the trick (tested with Gradle 7.3.2):
// TODO: replace this dummy task with the task from your Rust project which
// generates the Kotlin source directory. Make sure that the generated
// directory (with the Kotlin file(s)) is the task output directory.
val rustTask by tasks.registering(Copy::class) {
// To test this, I had simply put a Kotlin file into this "somewhere"
// directory.
from("somewhere")
into(temporaryDir)
}
sourceSets {
main {
java {
srcDir(rustTask)
}
}
}
tasks {
compileKotlin {
dependsOn(rustTask)
}
}
So, we’re simply adding the generated sources as an additional source directory to the default SourceSet which is consumed by the compileKotlin task. In addition, we make sure that the sources are generated before compileKotlin runs.
There is an xsd schema. It is necessary to generate Kotlin-classes according to the xsd description. How can I do this? Using the code below, I can get java classes. But I need kotlin classes
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
// Apply the application plugin to add support for building a CLI application.
id 'application'
/* Generate Java code from XSD */
id 'org.unbroken-dome.xjc' version '1.4.3'
}
group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
xjc {
includeInMainCompilation = false
}
xjcGenerate {
source = fileTree('src/main/schema') { include '*.xsd' }
bindingFiles = fileTree('src/main/jaxb') { include '*.xjb' }
catalogs = fileTree('src/main/catalog') { include '*.cat' }
}
sourceSets {
main { java { srcDir xjcGenerate.outputDirectory } }
}
compileKotlin {
dependsOn xjcGenerate
}
/* END: Make xjcGenerate work with Kotlin */
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
google()
}
dependencies {
/* Add JAXB dependencies for Java 11 */
implementation 'javax.xml.bind:jaxb-api:2.3.1'
// Use the Kotlin JDK 8 standard library.
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
// Use the Kotlin test library.
testImplementation 'org.jetbrains.kotlin:kotlin-test'
// Use the Kotlin JUnit integration.
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
}
// Define the main class for the application.
mainClassName = 'ru.goryacms.AppKt'
I've tried https://github.com/reaster/schema-gen. It generates Kotlin data classes with annotations, but it has bugs so that the generated code does not compile, however this is easy to fix. In my case I also had to fix many annotations and some property names. Still much better than manually writing code.
You can try https://github.com/SixRQ/KAXB - Kotlin based class generator to generate native Kotlin classes from an XML Schema:
This project is used to generate native Kotlin classes from an xsd schema, similar to the JAXB tool for Java. The project will include a plugin for gradle and Intellij IDEA. ...
Once the archive has been extracted run the
bin/kaxb --P <destination package> --S <schema file> --T <target directory>
I recently switched from old 1.2 multiplatform into 1.3. Difference is, there's one one build.gradle file per multiplatform module (I got 5 of them) so a lot less configuration.
However I can't seem to be able to configure creating runnable fat jar with all dependencies from jvm platform.
I used to use standard "application" plugin in my jvm project and jar task, but that does not work anymore. I found there's "jvmJar" task and I modified it (set Main-class), but created jar doesn't contain dependencies and crashes on ClassNotFoundException. How do I do it?
This is what I have now:
jvm() {
jvmJar {
manifest {
attributes 'Main-Class': 'eu.xx.Runner'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
I did hit that bump and used this work around.
1. Restructure your project
Lets call your project Project.
create another submodule say subA, which will have the gradle notation Project:subA
now, subA has your multiplatform code in it (It is the gradle project with apply :kotlin-multiplafrom) in its build.gradle
2. Add Another submodule
create another submodule which targets only jvm say subB, which will have the gradle notation Project:subB
So, subB will have plugins: 'application' and 'org.jetbrains.kotlin.jvm'
3. Add your module as a gradle dependency (see my build.gradle)
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
id "application"
}
apply plugin: "kotlinx-serialization"
group 'tz.or.self'
version '0.0.0'
mainClassName = "com.example.MainKt"
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation project(':subA')
}
you can proceed and build subB as you would a regular java project or even use the existing plugins, it will work
Got it working with the multiplatform plugin in kotlin 1.3.61:
The following works for a main file in src/jvmMain/kotlin/com/example/Hello.kt
Hello.kt must also specify its package as package com.example
I configured my jvm target in this way:
kotlin {
targets {
jvm()
configure([jvm]) {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'com.example.HelloKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
}
}
Got it to work with a slightly modified version of what luca992 did:
kotlin {
jvm() {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'sample.MainKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
...
}
The only way to get gradle/multiplatform working appears to be endless trial and error; It's a nightmare, it's not being built as a "build" system so much as a "build system"; to put it another way, these two tools (together or in isolation) are a means of implementing only a single software development life cycle that the plugin maker intended, however, if you've engineered a desired software lifecycle and CI/CD system and now your trying to implement that engineering, it will be MUCH harder to do it with these tools than it would be to do it with scripts, code or maven. There are a number of reasons for this:
Massive changing in coding convention due to the plugin makers only exposing bar minimum configurability, probably only giving access to the things they need for their own personal project.
Very poor documentation updates; Kotlin, gradle and plugins are changing so rapidly I have begun to seriously question the usefulness of these tools.
Thus, at the time of writing this seems to be the correct syntax to use when using kotlin 1.3.72, multiplatform 1.3.72, ktor 1.3.2 and gradle 6.2.2 (using the kts format).
Note the fatJar seems to assemble correctly but won't run, it can't find the class, so I included the second runLocally task I've been using in the mean time.
This isn't a complete solution so I hate posting it on here, but from what I can tell... it is the most complete and up to date solution I can find documented anywhere.
//Import variables from gradle.properties
val environment: String by project
val kotlinVersion: String by project
val ktorVersion: String by project
val kotlinExposedVersion: String by project
val mySqlConnectorVersion: String by project
val logbackVersion: String by project
val romeToolsVersion: String by project
val klaxonVersion: String by project
val kotlinLoggingVersion: String by project
val skrapeItVersion: String by project
val jsoupVersion: String by project
val devWebApiServer: String by project
val devWebApiServerVersion: String by project
//Build File Configuration
plugins {
java
kotlin("multiplatform") version "1.3.72"
}
group = "com.app"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
jcenter {
url = uri("https://kotlin.bintray.com/kotlin-js-wrappers")
}
maven {
url = uri("https://jitpack.io")
}
}
//Multiplatform Configuration
kotlin {
jvm {
compilations {
val main = getByName("main")
tasks {
register<Jar>("buildFatJar") {
group = "application"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = archiveVersion
attributes["Main-Class"] = "com.app.BackendAppKt"
}
archiveBaseName.set("${project.name}-fat")
from(main.output.classesDirs, main.compileDependencyFiles)
with(jar.get() as CopySpec)
}
register<JavaExec>("runLocally") {
group = "application"
setMain("com.app.BackendAppKt")
classpath = main.output.classesDirs
classpath += main.compileDependencyFiles
}
}
}
}
js {
browser { EXCLUDED FOR LENGTH }
}
sourceSets { EXCLUDED FOR LENGTH }
}