I have a java Android project and I am trying to include Kotlin/Convert some of the java classes to Kotlin.
Project’s build.gradle:
Here I have introduced a variable for Kotlin version 1.6.10 and kotlin gradle plugin.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
import com.tcs.dev.Repo
import org.gradle.wrapper.SystemPropertiesHandler
buildscript {
ext.kotlin_version = '1.6.10'
// load local.properties file like gradle.properties
System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(file('local.properties')))
Properties properties = new Properties()
if (file("local.properties").exists()) {
properties.load(file('local.properties').newDataInputStream())
}
if (project.hasProperty('propfile')) {
properties.load(file(propfile).newDataInputStream())
}
properties.each { k, v -> ext[k] = v }
repositories {
google()
maven Repo.config(project)
maven { url 'https://jitpack.io' }
maven { url "https://oss.sonatype.org/content/repositories/snapshots" } // For Spoon snapshot, until 2.0.0 is released
jcenter()
}
dependencies {
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.google.gms:google-services:4.3.10'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
App’s build.gradle:
Here I have introduced the Kotlin plugin and two more Kotlin libraries in the implementation section.
import com.tcs.dev.BuildVars
import com.tcs.dev.FileExtension
import com.tcs.dev.PropertiesFile
import com.tcs.dev.Repo
import com.tcs.dev.Shell
import com.tcs.dev.Version
import java.nio.file.Files
import java.nio.file.Paths
import java.util.regex.Pattern
buildscript {
// Items referenced both in and outside of the buildscript block
ext {
buildToolsDir = System.env.get('HOME') + "/build_tools"
localProperties = new File(rootProject.projectDir, "local.properties")
}
// SDK - needs to be done by buildscript because the android-sdk-manager plugin requires it on apply
def androidSdkName = getProperty('systemProp.tcs.dev.android.sdk.name')
def androidSdkVersion = getProperty('systemProp.tcs.dev.android.sdk.version')
def androidSdkClassifier = "darwin-x86_64"
def androidSdkExt = "tgz"
repositories {
jcenter()
google()
maven Repo.config(project)
}
dependencies {
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
classpath group: 'com.google', name: androidSdkName,
version: androidSdkVersion, ext: androidSdkExt, classifier: androidSdkClassifier
}
PropertiesFile.addOrChangeKey(project.localProperties, "sdk.dir", androidSdkDir)
// This should already exist, but just in case not
FileExtension.mkdirs(project.buildToolsDir)
// Extract the Android SDK
if (! new File(sdkBomFile).exists()) {
def sdkFile = sprintf("%s-%s-%s.%s", androidSdkName, androidSdkVersion, androidSdkClassifier, androidSdkExt)
def sdkFullFile = buildscript.configurations.classpath.files.find {
if (it.getName() == sdkFile) return it.getAbsoluteFile()
}
println "Extracting Android SDK"
def cmd = "tar -zxf ${sdkFullFile} -C ${project.buildToolsDir}"
def (exitValue, output) = Shell.shellCommandReturningExitValueAndOutput(cmd)
if (exitValue != 0) {
throw new Exception("Extract command exited with '${exitValue}': ${cmd}")
}
if (! new File(sdkBomFile).exists()) {
throw new Exception("Extract command did not create file '${sdkBomFile}': ${cmd}")
}
println "Accepting license agreements"
cmd = "yes | ${androidSdkDir}/tools/bin/sdkmanager --sdk_root=${androidSdkDir} --licenses"
(exitValue, output) = Shell.shellCommandReturningExitValueAndOutput(cmd)
}
}
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.firebase.crashlytics'
id 'com.google.gms.google-services'
}
// NDK
def androidNdkName = getProperty('systemProp.tcs.dev.android.ndk.name')
def androidNdkVersion = getProperty('systemProp.tcs.dev.android.ndk.version')
def androidNdkDir = sprintf("%s/%s-%s", project.buildToolsDir, androidNdkName, androidNdkVersion)
def androidNdkInstalled = {
return new File(androidNdkDir, 'ndk-build').exists()
}
// AVD
def androidAvdName = getProperty('systemProp.tcs.dev.android.avd.name')
def androidAvdVersion = getProperty('systemProp.tcs.dev.android.avd.version')
def androidAvdDir = sprintf("%s/%s-%s", project.buildToolsDir, androidAvdName, androidAvdVersion)
def androidAvdInstalled = {
return new File(androidAvdDir, "Nexus_9_API_${androidAvdVersion}.ini").exists()
}
[graphicsLibVersion:graphicsLibVersion, serializableLibVersion:serializableLibVersion].each { k, v ->
if (!v.startsWith(Version.projectVersion())) {
logger.warn "WARNING: ${k} ${v} does not match projectVersion ${Version.projectVersion()} please update"
}
}
def gitSha() {
return 'git rev-parse --short HEAD'.execute().text.trim()
}
configurations {
graphicsLib
eigen
generatedSource
manuallyEditedSourceDependencies
if (!androidNdkInstalled()) {
androidNdk
}
if (!androidAvdInstalled()) {
androidAvd
}
}
dependencies {
graphicsLib group: 'com.tcs', name: 'graphics-lib',
version: graphicsLibVersion, ext: 'tgz', classifier: 'sources'
generatedSource group: 'com.tcs', name: 'tcs-serializable-generator',
manuallyEditedSourceDependencies group: 'com.tcs', name: 'cppGraphicsDependencies',
version: cppGraphicsVersion, ext: 'tgz', classifier: 'sources'
if (!androidNdkInstalled()) {
androidNdk group: 'com.google', name: androidNdkName,
version: androidNdkVersion, ext: 'zip', classifier: 'darwin-x86_64'
}
if (!androidAvdInstalled()) {
androidAvd group: 'com.google', name: androidAvdName,
version: androidAvdVersion, ext: 'tgz', classifier: 'darwin-x86_64'
}
}
def minutesSinceEpoch() {
// This should produce unique values for > 4000 years
def minutes = new Date().time.intdiv(1000).intdiv(60).intdiv(30) * 30
Integer clamped = minutes & Integer.MAX_VALUE
return clamped
}
android {
compileSdkVersion 30
buildToolsVersion '30.0.0'
// Required for butterknife compatibility with androidx
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
defaultConfig {
applicationId = BuildVars.applicationId
minSdkVersion 21
targetSdkVersion 30
versionCode minutesSinceEpoch()
versionName "${Version.packageVersion()}"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
}
sourceSets.main {
// use the jni .so compiled from the manual ndk-build command'''
jniLibs.srcDirs = ['src/main/jniLibs/debug/lib', 'src/main/jniLibs/release/lib']
jni.srcDirs = [] //disable automatic ndk-build call
}
signingConfigs {
debug {
storeFile file("../tcs-debug.keystore")
storePassword "dummy1"
keyAlias "debug"
keyPassword "dummy1"
}
release {
storeFile file("../tcs-android.keystore")
storePassword project.properties.get("dummy2")
keyAlias "release"
keyPassword project.properties.get("dummy2")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.debug
applicationIdSuffix ".debug"
jniDebuggable true
ext.setBuildConfigFieldWithDefault = { fieldName, value ->
if (project.hasProperty(fieldName)) {
value = project.properties.get(fieldName)
}
buildConfigField "String", fieldName, ""$value""
}
def testMap = []
if (project.findProperty('testStack') == "staging") {
testMap = [
'testServer' :'https://staging.dev.tcs.com/',
'testAsmBrokenTestDocId':'a63f4467861f1c54500afd9d',
'testAsmBrokenTestWsId' :'9b6b7102fe2578a1d683adf1',
'testAnonymousDocId' :'346fa02ef804498723df9b6e'
]
} else {
testMap = [
'testServer' :'https://demo-c.dev.tcs.com/',
]
}
printf("testMap is: ${testMap}n")
testMap.each{ k, v -> setBuildConfigFieldWithDefault(k, v) }
setBuildConfigFieldWithDefault("testUsername", "[email protected]")
setBuildConfigFieldWithDefault("testPassword", "testPassword")
ext.enableCrashlytics = false
pseudoLocalesEnabled true
testCoverageEnabled true
}
}
project.gradle.taskGraph.whenReady {
connectedDebugAndroidTest {
ignoreFailures = true
}
}
packagingOptions {
exclude 'META-INF/rxjava.properties'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'LICENSE.txt'
doNotStrip "*/armeabi/*.so"
doNotStrip "*/armeabi-v7a/*.so"
doNotStrip "*/x86/*.so"
}
dexOptions {
javaMaxHeapSize "2g"
}
applicationVariants.all { variant ->
variant.mergeAssetsProvider.get().dependsOn(extractShaders)
}
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation', 'NotSibling'
}
}
android.applicationVariants.all { variant ->
def applicationId = variant.applicationId as String
//def adbPath = android.adbExe as String
def adbPath = "/platform-tools/adb"
def variantName = variant.name.capitalize()
String pd = projectDir as String
def adbPlus = pd + "/../buildSrc/adb+.sh"
def grantPermissionsTask = tasks.create("grant${variantName}Permissions") {
doLast {
"bash ${adbPlus} ${adbPath} ${applicationId} android.permission.READ_CONTACTS".execute()
"bash ${adbPlus} ${adbPath} ${applicationId} android.permission.WRITE_EXTERNAL_STORAGE".execute()
}
}
grantPermissionsTask.description = "Grants permissions on Marshmallow and later"
grantPermissionsTask.group = "extras"
}
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("compileReleaseJavaWithJavac")) {
theTask.dependsOn "ndkBuildRelease"
} else if (theTask.name.equals("compileDebugJavaWithJavac")) {
theTask.dependsOn "ndkBuildDebug"
}
}
def assetsDir = new File(projectDir, 'src/main/assets')
task createAssetsDir() {
doFirst {
FileExtension.mkdirs(assetsDir)
}
}
task extractEigen(type: Copy) {
description = 'Expand eigen files into $EIGEN_DIR'
from(tarTree(configurations.eigen.singleFile)) {
include '*/Eigen/*'
include '*/Eigen/src/**'
}
if (eigenVersion == '506565787cdc4') { // 3.1.2
into eigenDir
def root = Pattern.compile('.*/Eigen/')
eachFile {
it.mode |= 0220
it.path = it.path.replaceFirst(root, '')
}
} else {
into eigenDir
eachFile {
it.mode |= 0220
}
}
dirMode 0775
}
task extractShaders(type: Exec) {
dependsOn createAssetsDir
def wd = assetsDir
def extractDir = 'shaders'
def targetFile = 'shaders'
inputs.file configurations.graphicsLib.singleFile
// Ensure the shaders are extracted
outputs.upToDateWhen { false }
outputs.dir "${wd}/${extractDir}"
workingDir wd
commandLine 'tar', '-s', ",graphics-lib-${graphicsLibVersion}/${targetFile},${extractDir},", '-xzf', configurations.graphicsLib.singleFile, "graphics-lib-${graphicsLibVersion}/${targetFile}"
}
task extractGraphics(type: Exec) {
description = 'set property graphics.repo.dir if you want to use your own version of the graphics repo.' +
' e.g. put a line like this in local.properties: graphics.repo.dir=/Users/pkania/repos/master/graphics'
def wd = 'src/main/jni'
def extractDir = 'Graphics'
def targetFile = 'GraphicsLibrary'
def graphicsDir = "${wd}/${extractDir}"
// Ensure the graphics are extracted
outputs.upToDateWhen { false }
outputs.dir graphicsDir
workingDir wd
doFirst {
def path = Paths.get(graphicsDir)
if (Files.isSymbolicLink(path)) {
Files.delete(path)
}
}
def graphicsRepoDir = project.properties.get('graphics.repo.dir')
if (graphicsRepoDir) {
def script = """
if [ -d ${extractDir} ]; then
rm -rf ${extractDir}
fi
ln -sf ${graphicsRepoDir}/BTGraphicsLibrary ${extractDir}
"""
commandLine Shell.getShellCommandLine(script)
} else {
inputs.file configurations.graphicsLib.singleFile
commandLine 'tar', '-s', ",graphics-lib-${graphicsLibVersion}/${targetFile},${extractDir},", '-xzf', configurations.graphicsLib.singleFile, "graphics-lib-${graphicsLibVersion}/${targetFile}"
}
}
task getGeneratedSource(type:Exec) {
description = 'extract cppGraphics generated source'
dependsOn extractGraphics
def tarFile = configurations.generatedSource.singleFile.path
inputs.file tarFile
workingDir 'src/main/jni'
// Ensure the cpp graphics are extracted
outputs.upToDateWhen { false }
def cmd = Shell.getShellCommandLine("tar xf ${tarFile}")
commandLine cmd
}
task getManuallyEditedSourceDependencies(type:Exec) {
description = 'extract cppGraphics manually edited source'
// this task must run after the generated sources are copied because manually edited files can
// overwrite generated ones.
dependsOn getGeneratedSource
def tarFile = configurations.manuallyEditedSourceDependencies.singleFile.path
inputs.file tarFile
workingDir 'src/main/jni/cppGraphics'
def cmd = Shell.getShellCommandLine("tar xf ${tarFile}")
commandLine cmd
}
task getAndroidNdkDir(type:Exec) {
}
getAndroidNdkDir.onlyIf {
!androidNdkInstalled()
}
task getAndroidNdk() {
}
task createSwigOutputDir {
doLast {
def swigOutputDir = file('src/main/java/com/tcs/app/graphics/gen')
FileExtension.mkdirs(swigOutputDir)
}
}
task getAndroidAvdDir(type:Exec) {
}
getAndroidAvdDir.onlyIf {
!androidAvdInstalled()
}
task getAndroidAvd() {
}
task swigBuild(type: Exec) {
dependsOn extractGraphics
dependsOn getGeneratedSource
dependsOn getManuallyEditedSourceDependencies
dependsOn createSwigOutputDir
dependsOn extractEigen
workingDir 'src/main/jni'
commandLine '/usr/local/bin/swig', '-java', '-c++', '-package', 'com.tcs.app.graphics.gen', '-outdir', '../java/com/tcs/app/graphics/gen', '-o', './graphics_wrap.cpp', 'graphics.i'
}
def numCompilationThreads = {
def (exitValue, numCompileThreads) = Shell.shellCommandReturningExitValueAndOutput("sysctl -n hw.ncpu");
if (exitValue != 0) {
return 1
}
return numCompileThreads
}
// call regular ndk-build(.cmd) script from app directory
// http://stackoverflow.com/questions/16667903/android-studio-gradle-and-ndk
// http://ph0b.com/android-studio-gradle-and-ndk-integration/
// TODO: We'd rather not have two tasks here. Either research whether we can just use the default NDK build with our own Android.mk, or figure out how to streamline this w/Gradle.
task ndkBuildDebug(type: Exec) {
}
task ndkBuildRelease(type: Exec) {
}
// TODO: do not brute force delete the graphics generated files. Instead, tell gradle they are output files, and
// let gradle automatically clean them
task cleanGraphicsGen(type: Delete) {
}
clean.dependsOn(cleanGraphicsGen)
// TODO: This is a more complete way to get the dependencies in place, but we need to figure out how to get the
// TODO: buildtype so we can refer to the correct ndkBuild task...
//tasks.withType(JavaCompile) {
// compileTask -> compileTask.dependsOn ndkBuild
//}
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
implementation 'androidx.viewpager:viewpager:1.0.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation("com.tcs:tcs-android:$serializableLibVersion") {
transitive = false
}
implementation("com.tcs:tcs-primogenitor:$serializableLibVersion") {
transitive = false
}
implementation("com.tcs:tcs-serialize-common:$serializableLibVersion") {
transitive = false
}
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation 'com.google.android.gms:play-services-analytics:16.0.8'
implementation 'com.google.firebase:firebase-core:18.0.0'
implementation 'com.google.firebase:firebase-analytics:18.0.0'
implementation "com.google.firebase:firebase-messaging:17.5.0"
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.0.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
implementation 'com.google.code.gson:gson:2.6.2'
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
implementation("com.google.guava:guava:31.0.1-android")
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
implementation 'commons-lang:commons-lang:2.6'
implementation 'org.slf4j:slf4j-api:1.7.13'
implementation 'com.melnykov:floatingactionbutton:1.3.0'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'net.danlew:android.joda:2.9.9.4'
implementation 'io.github.inflationx:calligraphy3:3.1.1'
implementation 'io.github.inflationx:viewpump:2.0.3'
]
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1') {
exclude module: 'support-annotations'
}
androidTestImplementation('androidx.test.espresso:espresso-idling-resource:3.1.1') {
exclude module: 'support-annotations'
}
androidTestImplementation "com.squareup.spoon:spoon-client:2.0.0-SNAPSHOT" // For Spoon snapshot, until 2.0.0 is released
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0', {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.android.support', module: 'support-v4'
exclude group: 'com.android.support', module: 'design'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.constraintlayout:constraintlayout-solver:1.1.3'
androidTestImplementation 'junit:junit:4.13'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
}
spoon {
def envSerial = System.env['ANDROID_SERIAL'];
if (envSerial) {
devices = [envSerial];
}
if (project.hasProperty('spoonClassName')) {
className = project.spoonClassName
}
if (project.hasProperty('spoonMethodName')) {
methodName = project.spoonMethodName
}
debug = true
adbTimeout = 10*60
// className = 'com.tcs.integration.smoketests'
// methodName = 'testSteeringWheel'
}
I get this error when gradle syncs:
Cause 1: com.android.build.gradle.internal.crash.ExternalApiUsageException: java.lang.IllegalArgumentException: Cannot change attributes of dependency configuration ':app:debugCompile' after it has been resolved
Caused by: java.lang.IllegalArgumentException: Cannot change attributes of dependency configuration ':app:debugCompile' after it has been resolved
at org.gradle.api.internal.attributes.ImmutableAttributeContainerWithErrorMessage.attribute(ImmutableAttributeContainerWithErrorMessage.java:57)
Cause 2: org.gradle.api.UnknownDomainObjectException: KotlinJvmAndroidCompilation with name 'debug' not found
2
Answers
I saw messages like these in the gradlew --debug output. RuntimeException: Configuration 'eigen' was resolved during configuration time
I ignored them at first because they didn't seem related to the exception displayed on the console.
I created an empty andriod studio project (with the kotlin-andorid plugin) and verified that could "build" without error. I then added parts of our app/build.gradle file into the new project until I encountered the ':app:debugCompileOnly' error.
The error occurred when I added the extractEigen task. That reminded me of the configuration resolution error I saw in the debug output.
I fixed the configuration resolution error and that fixed the ':app:debugCompileOnly'error.
(Fixed by a colleague)
There is no relevant code at all …but according to the error message:
I’d suggest to define
android.buildTypes.debug
, so that it would be known:But the answer may eventually rather be, to add Crashlytics on class-path (root
build.gradle
):