org.gradle.api.UnknownDomainObjectException:未找到名称为 'debug' 的 KotlinJvmAndroidCompilation
org.gradle.api.UnknownDomainObjectException: KotlinJvmAndroidCompilation with name 'debug' not found
我有一个 java Android 项目,我正在尝试将 Kotlin/Convert 一些 java 类 包含到 Kotlin 中。
项目的build.gradle:
这里我已经为 Kotlin 版本 1.6.10 和 kotlin gradle 插件引入了一个变量。
// 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"
}
}
应用的 build.gradle:
这里我在实现部分介绍了Kotlin插件和另外两个Kotlin库
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", "androidtester3@test.tcs.com")
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://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'
}
我在 gradle 同步时收到此错误:
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
根本没有相关代码...但是根据报错信息:
KotlinJvmAndroidCompilation with name 'debug' not found
我建议定义 android.buildTypes.debug
,这样就可以知道:
android {
buildTypes {
debug {}
}
}
但最终答案可能是,在 class-路径(root build.gradle
)上添加 Crashlytics:
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
我在 gradlew --debug 输出中看到了这样的消息。
RuntimeException:配置 'eigen' 已在配置时间
期间解决
起初我忽略了它们,因为它们似乎与控制台上显示的异常无关。
我创建了一个空的 andriod studio 项目(使用 kotlin-andorid 插件)并验证它可以“构建”而不会出错。
然后,我将 app/build.gradle 文件的部分内容添加到新项目中,直到遇到“:app:debugCompileOnly”错误。
添加extractEigen任务时出现错误。
这让我想起了我在调试输出中看到的配置解析错误。
我修复了配置解析错误并修复了“:app:debugCompileOnly”错误。
(由同事修复)
我有一个 java Android 项目,我正在尝试将 Kotlin/Convert 一些 java 类 包含到 Kotlin 中。
项目的build.gradle:
这里我已经为 Kotlin 版本 1.6.10 和 kotlin gradle 插件引入了一个变量。
// 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"
}
}
应用的 build.gradle:
这里我在实现部分介绍了Kotlin插件和另外两个Kotlin库
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", "androidtester3@test.tcs.com")
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://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'
}
我在 gradle 同步时收到此错误:
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
根本没有相关代码...但是根据报错信息:
KotlinJvmAndroidCompilation with name 'debug' not found
我建议定义 android.buildTypes.debug
,这样就可以知道:
android {
buildTypes {
debug {}
}
}
但最终答案可能是,在 class-路径(root build.gradle
)上添加 Crashlytics:
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
我在 gradlew --debug 输出中看到了这样的消息。 RuntimeException:配置 'eigen' 已在配置时间
期间解决起初我忽略了它们,因为它们似乎与控制台上显示的异常无关。
我创建了一个空的 andriod studio 项目(使用 kotlin-andorid 插件)并验证它可以“构建”而不会出错。 然后,我将 app/build.gradle 文件的部分内容添加到新项目中,直到遇到“:app:debugCompileOnly”错误。
添加extractEigen任务时出现错误。 这让我想起了我在调试输出中看到的配置解析错误。
我修复了配置解析错误并修复了“:app:debugCompileOnly”错误。
(由同事修复)