Trying to create some playground over Detekt custom rules and it just doesn't work. Doesn't even try to find the rule-set file.
The gradle goes
plugins {
id 'com.android.application'
id 'kotlin-android'
id("io.gitlab.arturbosch.detekt").version("1.17.1")
}
...
detekt {
toolVersion = "$detekt_version" // 1.17.1
input = files("src/main/java")
config = files("../detekt/detekt-config.yml")
autoCorrect = true
reports { ... }
}
dependencies {
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
compileOnly "io.gitlab.arturbosch.detekt:detekt-api:$detekt_version"
detekt "io.gitlab.arturbosch.detekt:detekt-cli:$detekt_version"
...
}
The rule goes
class SomeCustomRule(config: Config) : Rule(config) {
override val issue: Issue
get() = Issue("Import thingy", Severity.Minor, "I don't like this string", Debt(10))
override fun visitImportDirective(importDirective: KtImportDirective) {
val import: String = importDirective.importPath?.pathStr ?: ""
if ("appcompat" in import) {
report(CodeSmell(
issue,
Entity.from(importDirective),
"Importing $import which is an internal import"))
}
}
}
The provider is
class CustomRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "detekt-custom-rules"
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(SomeCustomRule(config)))
}
And last but not least
At
src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
is
com.playgrounds.detekt.customrule.CustomRuleSetProvider
Still - nothing. I tried to add gibberish at the provider - nothing, no error. I even tried to spoil the declaration at META-INF. Nothing.
Of course I tried to add the provider into my config file, or add a project line in the gradle. Error, not recognised.
What have I missed?
Thanks
Related
I created a gradle plugin to unify some settings between the various modules of the application. the summary of the error is this:
org.gradle.api.plugins.InvalidPluginException: An exception occurred applying plugin request [id: 'common.plugin']
Caused by: org.gradle.api.UnknownDomainObjectException: Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension,....]
This is a summary image of the project architecture:
CommonPluginClass:
class CommonPluginClass : Plugin<Project> {
private val consumerProguardFileName = "consumer-rules.pro"
private val proguardFileName = "proguard-rules.pro"
private val sdkToCompile = 33
override fun apply(project: Project) {
println(">>> Adding sugar to gradle files!")
with(project)
{
applyPlugins(this)
androidConfig(this)
}
println(">>> Sugar added for core Module!")
}
private fun applyPlugins(project: Project) {
println(">>> apply plugins!")
project.pluginManager.run {
apply("com.android.application")
apply("kotlin-android")
apply("kotlin-kapt")
}
println(">>> end apply plugins!")
}
private fun androidConfig(project: Project) {
project.extensions.configure<LibraryExtension>{
defaultConfig.targetSdk = 33
}
}
}
the error occurs inside the androidConfig function when calling configure
I was inspired by now android, dependencies and imports are similar but not build. Can someone unlock it please.
build-logic:convention build.gradle
plugins {
alias(libs.plugins.kotlin.jvm) apply false
id "org.gradle.kotlin.kotlin-dsl" version "2.4.1"
}
dependencies {
compileOnly(libs.android.pluginGradle)
compileOnly(libs.kotlin.pluginGradle)
}
gradlePlugin {
plugins {
commonPlugin {
id = "common.plugin"
implementationClass = "CommonPluginClass"
}
}
}
LITTLE UPDATE:
I've noticed that any module I enter always identifies it to me as ApplicationExtension
I found the solution and as usual it's a stupid thing.
the application extension use plugin
apply("com.android.application")
the library or module use plugin
apply("com.android.library")
I have two multiplatform modules shared and other in a standard multiplatform template project that targets Android and iOS.
shared defines a class in commonMain source set
class SharedGreeting()
other is setup to depend on shared like this in the gradle file:
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
And in its androidMain sourceset it tries to reference SharedGreeting in some class, fx:
class AndroidGreeter{
val foo = SharedGreeting()
}
But no matter what I try, I get IDE errors when I try to reference the shared class, and I have to manually add an import statement.
The code compiles and deploys without problems though!
Any ideas on what I am missing or misunderstanding? Or is this a bug in KMM?
Full copy of other gradle file:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
}
version = "1.0"
kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
ios.deploymentTarget = "14.1"
framework {
baseName = "other"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 24
targetSdk = 32
}
}
For full project source code:
https://github.com/RabieJradi/kmm_import_error_sample
IDE suggested action for adding a dependency doesn't unfortunately do anything.
Turns out it is a bug in KMM and a fix have been implemented by the Jetbrains team here https://youtrack.jetbrains.com/issue/KTIJ-22056/KMM-Cant-reference-shared-module-commonMain-source-set-classes-from-another-modules-androidMain-source-set
You cannot do this in that way.
To make your project work you have to work with expect and actual words.
So, make these changes:
change your SharedGreeting class in shared module like that:
expect class SharedGreeting {
fun greeting(): String
}
Then your IDE wants you to make two changes, in shared module.
Add a class in both modules, iosMain and androidMain named SharedGreeting. The code is the same for both classes:
actual class SharedGreeting {
actual fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}
The work is done, now your other library has no errors. From your androidMain module inside "other" you can work only on other androidMain modules inside other MKK libraries.
I'm still new in developing Gradle Plugin. But what i want to do is to define global method to call in consumer gradle file.
So my plugin class looking something like this:
class Main : Plugin<Project> {
override fun apply(target: Project) {
// Some configuration
}
fun modulePath(moduleName: String, target: Project): Any {
val propFile = File("${target.rootDir}/local.properties")
if (!propFile.exists()) propFile.createNewFile()
val props = Properties()
props.load(propFile.inputStream())
val useAAR = props.getProperty("useAAR", "false")?.toBoolean() ?: false
val devModule = props.getProperty("modules", ":app").split(" ")
return if (useAAR && !devModule.contains(moduleName)) {
"$groupId:$moduleName:$versionId"
} else {
target.project(moduleName)
}
}
companion object {
const val groupId = "local"
const val versionId = "0.1"
}
}
I can apply the plugin just fine in the consumer. But I still don't know how to call modulePath method in consumer's gradle file. Something like this:
apply plugin: 'main'
dependencies {
implementation modulePath(':someModule')
}
When i'm calling the modulePath, i'm getting error that the method is not defined.
Thanks in advance.
I am trying to use Android logs in my shared code so wanted to make use of the 'expected/actual' functionality in order to make the android side use logs to be read in log cat. However I cannot get the android module(not app module) to import the android.util.Log.
I have seen this answer but it did not work for me. I cannot get the import to resolve.
I think I need to implement a specific dependency in order to have access to the import but I'm not sure what that is.
Here is my build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
}
kotlin {
//select iOS target platform depending on the Xcode environment variables
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
::iosArm64
else
::iosX64
iOSTarget("ios") {
binaries {
framework {
baseName = "SharedCode"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("io.ktor:ktor-client:1.0.0-beta-3")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2")
// implementation ("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib") //Allows _androidMain to have java imports
implementation("io.ktor:ktor-client-android:1.0.0-beta-3")
api("org.jetbrains.kotlin:kotlin-stdlib:1.3.61")
api("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets
.getByName<KotlinNativeTarget>("ios")
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
Here you got JVM target with the name "android" instead of actually Android target. The same problem occurred in the linked question. Can you tell, what's going on when you use the script from the answer? It seems like that one should work correctly.
As described in the documentation, one has to use an Android-specific Gradle plugin to make the Android target available. If you want to see how it can be done, consider having a look at this sample.
I had the same problem. Try using android() instead of only the jvm("android").
Also I've added my dependencies to android with android.sourceSets.foreach{ _ ->
dependencies{ ... }
}
Just fixed same issue, finally used this tutorial https://medium.com/icerock/how-to-start-use-kotlin-multiplatform-for-mobile-development-1d3022742178
so my build.gradle looks like:
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 14
targetSdkVersion 29
}
}
kotlin {
targets {
android()
iosArm64()
iosX64()
}
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
}
}
I used this link to create our own custom lint rules
link
then create WrongLayoutDetector.class to detect wrong layout name
private val allowedPrefixes = listOf("activity_", "view_", "fragment_", "dialog_", "bottom_sheet_", "adapter_item_", "divider_", "space_", "popup_window_")
val ISSUE_WRONG_LAYOUT_NAME = Issue.create("WrongLayoutName",
"Layout names should be prefixed accordingly.",
"The layout file name should be prefixed with one of the following: ${allowedPrefixes.joinToString()}. This will improve consistency in your code base as well as enforce a certain structure.",
CORRECTNESS, 7, WARNING,
Implementation(WrongLayoutNameDetector::class.java, RESOURCE_FILE_SCOPE))
class WrongLayoutNameDetector : LayoutDetector() {
override fun visitDocument(context: XmlContext, document: Document) {
val modified = allowedPrefixes.map {
val resourcePrefix = context.project.resourcePrefix()
.forceUnderscoreIfNeeded()
if (resourcePrefix != it) resourcePrefix + it else it
}
val doesNotStartWithPrefix = modified.none { context.file.name.startsWith(it) }
val notEquals = modified.map {
it.dropLast(1) // Drop the trailing underscore.
}.none { context.file.name == "$it.xml" }
if (doesNotStartWithPrefix && notEquals) {
context.report(ISSUE_WRONG_LAYOUT_NAME, document, context.getLocation(document), "Layout does not start with one of the following prefixes: ${modified.joinToString()}")
}
}
}
private fun String.forceUnderscoreIfNeeded() = if (isNotEmpty() && !endsWith("_")) plus("_") else this
fun Project.resourcePrefix() = if (isGradleProject) computeResourcePrefix(gradleProjectModel).orEmpty() else ""
IssueRegistry class
class IssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf( ISSUE_WRONG_LAYOUT_NAME,ISSUE_ALERT_DIALOG_USAGE)
override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
}
build gradle file
apply plugin: 'java-library'
dependencies {
// For a description of the below dependencies, see the main project README
compileOnly "com.android.tools.lint:lint-api:26.2.0-rc02"
compileOnly "com.android.tools.lint:lint-checks:26.2.0-rc02"
testCompile "junit:junit:4.12"
testCompile "com.android.tools.lint:lint:26.2.0-rc02"
testCompile "com.android.tools.lint:lint-tests:26.2.0-rc02"
testCompile "com.android.tools:testutils:26.2.0-rc02"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
jar {
manifest {
// Only use the "-v2" key here if your checks have been updated to the
// new 3.0 APIs (including UAST)
attributes("Lint-Registry-v2": "com.example.lint.checks.IssueRegistry")
}
}
now in our code
lint.xml file
<lint>
<!-- Change the severity of hardcoded strings to "error" -->
<issue id="WrongLayoutName"
severity="warning" />
</lint>
and in app gradle file
lintOptions {
tasks.lint.enabled = true
abortOnError true
lintConfig file('./code_quality_tools/lint.xml')
}
now i am analyze code but not getting any warning like WrongLayoutName.
How could i implement custom lint rules with our app?
if i add <issue id="AllCaps" /> in lint file its working fine and getting warning
You have to add the link-check module in your dependency:
If you have this structure:
lint-check
-- build.gradle
app
-- build.gradle
settings.gradle
In settings.gradle:
include ':app'
include ':lint-checks'
In app/build.gradle:
dependencies {
lintChecks project(':lint-checks')
}