Timur Abdyraev
07/10/2024, 4:20 PMA problem occurred configuring root project 'Test'.
> Could not resolve all files for configuration ':classpath'.
> Could not find clean-wizard-root.gradle-plugins:api:unspecified.
Searched in the following locations:
- <https://plugins.gradle.org/m2/clean-wizard-root/gradle-plugins/api/unspecified/api-unspecified.pom>
Required by:
project : > io.github.timbermir.cleanwizard.multimodule:io.github.timbermir.cleanwizard.multimodule.gradle.plugin:2.0.0-RC1 > io.github.timbermir:implementation:2.0.0-RC1
Those are my plugins:
1. https://plugins.gradle.org/plugin/io.github.timbermir.cleanwizard
2. https://plugins.gradle.org/plugin/io.github.timbermir.cleanwizard.multimodule
You can see the Gradle plugin implementation class hereThomas Broyer
07/10/2024, 4:59 PMThomas Broyer
07/10/2024, 5:05 PMplugins {
id("io.github.timbermir.cleanwizard.multimodule") version "2.0.0-RC1"
}
Gradle will look for https://plugins.gradle.org/m2/io/github/timbermir/cleanwizard/multimodule/io.github.ti[…]mbermir.cleanwizard.multimodule.gradle.plugin-2.0.0-RC1.pom (known as a "plugin marker") which has a dependency on the actual project artifact, whose POM is at https://plugins.gradle.org/m2/io.github.timbermir/implementation/2.0.0-RC1/implementation-2.0.0-RC1.pom (Gradle will actually load the module metadata at https://plugins.gradle.org/m2/io.github.timbermir/implementation/2.0.0-RC1/implementation-2.0.0-RC1.module)Timur Abdyraev
07/10/2024, 6:00 PMplugin-api.build.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
alias(libs.plugins.kotlin.dsl)
alias(libs.plugins.google.devtools.ksp)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.gradle.publish)
alias(libs.plugins.gradle.signing)
alias(libs.plugins.shadow)
}
tasks.withType<ShadowJar> {
archiveClassifier.set("")
}
dependencies {
compileOnly(project(":gradle-plugins:api"))
implementation(libs.kotlinx.serialization.json)
implementation(libs.google.gson)
implementation(libs.vanniktech.maven.publish)
compileOnly(libs.kotlin.gradle.plugin)
compileOnly(libs.google.devtools.ksp)
compileOnly(files(ksp.javaClass.superclass.protectionDomain.codeSource.location))
compileOnly(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
compileOnly(files(projectConfig.javaClass.superclass.protectionDomain.codeSource.location))
}
group = projectConfig.versions.group.get()
version = projectConfig.versions.version.get()
repositories {
mavenCentral()
}
gradlePlugin {
website.set("<https://github.com/Timbermir/clean-wizard>")
vcsUrl.set("<https://github.com/Timbermir/clean-wizard.git>")
plugins {
libs.plugins.cleanwizard.apply {
create("cleanwizard-gradle-plugin") {
id = core.pluginId
implementationClass = pluginConfig.versions.cleanwizard.root.second.implementation.get()
displayName = "Clean Wizard Root Project Plugin"
description =
"This plugin is required for Clean Wizard Kotlin Symbol Processor advanced configuration. " +
"Using the following plugin you are able to configure how classes are generated," +
" for instance, using this plugin, you are able to change the DTO classes prefix from DTO to Dto."
tags.set(
listOf(
"kotlin",
"ksp",
"kotlin-symbol-processing",
"android",
"clean-architecture",
"domain-model",
"ui-model",
"dto-entity-mapper",
)
)
}
create("cleanwizard-multimodule-gradle-plugin") {
id = multimodule.pluginId
implementationClass = pluginConfig.versions.cleanwizard.multimodule.second.implementation.get()
displayName = "Clean Wizard Multi Module Plugin"
description =
"This plugin is required for Clean Wizard Kotlin Symbol Processor multi-module class generation. " +
"If your project is single-module, no need to apply it" +
". It allows you" +
"to generate classes in various modules, so if you have data, domain and presentation modules and want to generate classes" +
" for each module, use this plugin. "
tags.set(
listOf(
"kotlin",
"ksp",
"kotlin-symbol-processing",
"android",
"clean-architecture",
"domain-model",
"ui-model",
"dto-entity-mapper",
)
)
}
}
}
}
internal val Provider<PluginDependency>.pluginId
get() = get().pluginId
api.build.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.dsl)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.shadow)
alias(libs.plugins.maven.publish)
alias(libs.plugins.maven.signing)
}
tasks.withType<ShadowJar> {
archiveClassifier.set("")
}
dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.google.gson)
implementation(libs.squareup.moshi)
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.values()
.first { it.version == projectConfig.versions.kotlin.get() }
}
}
publishing {
publications {
create<MavenPublication>("shadow") {
groupId = projectConfig.versions.group.get()
artifactId = projectConfig.versions.artifact.get() + "-gradle-plugin-api"
version = projectConfig.versions.version.get()
artifact(tasks["shadowJar"])
pom {
inceptionYear.set("2024")
name.set("io.github.timbermir.cleanwizard:gradle-plugin-api")
url.set("<https://github.com/Timbermir/clean-wizard>")
description.set("Clean Wizard Plugin API")
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("<https://www.apache.org/licenses/LICENSE-2.0.txt>")
}
}
scm {
url.set("<https://github.com/Timbermir/clean-wizard>")
connection.set("scm:git:<git://github.com/Timbermir/clean-wizard.git>")
developerConnection.set("scm:git@github.com:Timbermir/clean-wizard.git")
}
issueManagement {
system.set("GitHub Issues")
url.set("<https://github.com/Timbermir/clean-wizard/issues/>")
}
pom.organization {
name.set("Timbermir")
url.set("<https://github.com/Timbermir>")
}
developers {
developer {
id.set(findProperty("developerId").toString())
name.set("timplifier")
email.set("<mailto:timplifier@gmail.com|timplifier@gmail.com>")
url.set("<https://github.com/timplifier>")
}
}
}
}
}
}Javi
07/10/2024, 7:24 PMTimur Abdyraev
07/11/2024, 9:41 AMJavi
07/11/2024, 9:53 AM{
"amount": {
"valueInCents": 230
"currency": "EUR"
}
}
Your backend model could be, simplifying units to Int and String:
data class MoneyDTO(
val valueInCents: Int,
val currency: String,
)
But I don't want to have that model in the domain, I would prefer to have something like
value class Money(private val value: String) {
val amount: Double = ...
val valueInCents: Int = ...
val currency: Currency = ...
enum class Currency(val code: String) {
EUR(code = "EUR"),
....
}
}
It is not possible to generate it as it is not a 1:1 mapper. That is the same for the UI (I don't like UI models; they are overkill to me).
UI model should expose everything (or almost everything) as String, as you are going to draw it, so your UI model would be
// depending on you need to print both, amount and currency at the same time or in two different components
data class MoneyUI(val amount: String, val currency: String, val vamountAndCurrency: String)
So for this simple example, you can see I don't want a 1:1 map in the three models, indeed, if you are going to map 1:1, wouldn't it be simple to create just the domain model and in data you can autogenerate a serializer (supposing you are using Kotlin Serialization for example) so you don't break clean arch as you are not annotating/extending your domain model. UI model can just be deleted, it does not provide any value and you can use the domain directly in the UI.Javi
07/11/2024, 9:57 AM@Composable
fun SomeScreen(money: Money) {
Text(text = money.format())
}
Creating a copy of Money with the name MoneyUI, providing a String value, just to remove the format extension function is overkill IMO, as, indeed, you are just moving the format call to a toMoneyUI call elsewhere.Javi
07/11/2024, 9:59 AMTimur Abdyraev
07/15/2024, 4:17 PMformat function requires external configuration because I don't know how to generate it due to lack of the context, though you can create an extension function for this class and then do whatever you want. Moreover, you can generate what you want and skip generation if it doesn't fit your requirements.
I guess this library fits most of the cases, because it is 1:1 mapping and you can do whatever you want with generated classes, except, of course editing them.
I hope you understand me that it is really hard to do those kind of things with generation where external configuration is a must.
Hope you got me.