plugins { id 'com.android.library' id 'maven-publish' id 'jacoco' } // Force Jacoco to compatible version for Gradle 8.x + JDK 17 jacoco { toolVersion = "0.8.7" } group = 'com.github.anggastudio' version = '1.0.1-SNAPSHOT' android { namespace 'com.anggastudio.printama' compileSdk 36 defaultConfig { minSdk 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' consumerProguardFiles 'consumer-rules.pro' debuggable true } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' consumerProguardFiles 'consumer-rules.pro' } } buildFeatures { buildConfig = true } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } publishing { // Publishes "release" build component created by // Android Gradle plugin singleVariant("release") { withSourcesJar() } } testOptions { unitTests { // Required for Robolectric + resources includeAndroidResources = true all { jacoco { includeNoLocationClasses = true excludes = ['jdk.internal.*'] // Exclude JDK internal classes from instrumentation } // IMPORTANT: Open JDK modules for reflection/instrumentation on JDK 17+ jvmArgs( '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.base/java.lang.reflect=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED', '--add-opens', 'java.base/jdk.internal.reflect=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.reflect=ALL-UNNAMED' ) // Force Gradle test workers to run on Java 17 javaLauncher = project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } maxParallelForks = 1 } } } } afterEvaluate { publishing { publications { release(MavenPublication) { from components.release artifactId = 'Printama' pom { name = 'Printama' description = 'Android library for Bluetooth thermal printing' } } } repositories { mavenLocal() } } } dependencies { implementation libs.appcompat implementation libs.material // Unit Testing Dependencies testImplementation libs.junit testImplementation libs.mockito.core testImplementation libs.mockito.inline testImplementation libs.robolectric testImplementation libs.powermock.module.junit4 testImplementation libs.powermock.api.mockito2 testImplementation libs.core testImplementation libs.ext.junit // Android Testing Dependencies androidTestImplementation libs.ext.junit androidTestImplementation libs.espresso.core androidTestImplementation libs.runner androidTestImplementation libs.rules androidTestImplementation libs.fragment.testing androidTestImplementation libs.mockito.android } // Ensure all Test tasks inherit strict single-fork, JDK 17, and module opens tasks.withType(Test).configureEach { maxParallelForks = 1 forkEvery = 0 jvmArgs( '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.base/java.lang.reflect=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED', '--add-opens', 'java.base/java.util=ALL-UNNAMED', '--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED', '--add-opens', 'java.base/jdk.internal.reflect=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.reflect=ALL-UNNAMED' ) javaLauncher = project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } } // Generate HTML + XML coverage report for unit tests // tasks.register('jacocoTestReport', JacocoReport) tasks.register('jacocoTestReport', JacocoReport) { dependsOn 'testDebugUnitTest' reports { html.required = true xml.required = true } def fileFilter = [ '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*$*' // synthetic/anonymous classes ] // Use provider-based paths instead of $buildDir def javaClasses = layout.buildDirectory.dir("intermediates/javac/debug/classes") def kotlinClasses = layout.buildDirectory.dir("tmp/kotlin-classes/debug") classDirectories.from = files( javaClasses.map { it.asFileTree.matching { exclude fileFilter } }, kotlinClasses.map { it.asFileTree.matching { exclude fileFilter } } ) sourceDirectories.from = files('src/main/java') executionData.from = layout.buildDirectory.asFileTree.matching { include "jacoco/testDebugUnitTest.exec" include "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" include "**/*.exec" include "**/*.ec" } } // Enforce minimum coverage for unit tests // tasks.register('jacocoTestCoverageVerification', JacocoCoverageVerification) tasks.register('jacocoTestCoverageVerification', JacocoCoverageVerification) { dependsOn 'testDebugUnitTest' def fileFilter = [ '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*$*' ] // Use provider-based paths instead of $buildDir def javaClasses = layout.buildDirectory.dir("intermediates/javac/debug/classes") def kotlinClasses = layout.buildDirectory.dir("tmp/kotlin-classes/debug") classDirectories.from = files( javaClasses.map { it.asFileTree.matching { exclude fileFilter } }, kotlinClasses.map { it.asFileTree.matching { exclude fileFilter } } ) sourceDirectories.from = files('src/main/java') executionData.from = layout.buildDirectory.asFileTree.matching { include "jacoco/testDebugUnitTest.exec" include "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec" include "**/*.exec" include "**/*.ec" } violationRules { rule { limit { counter = 'INSTRUCTION' value = 'COVEREDRATIO' minimum = 0.60 } } } } // Make 'check' fail if coverage is below threshold tasks.named('check') { dependsOn 'jacocoTestCoverageVerification' } tasks.withType(JavaCompile).configureEach { options.compilerArgs += ['-parameters'] }