app/build.gradle
///////////////////////////////////////////////////////////////////////////////
// Apply plugins
///////////////////////////////////////////////////////////////////////////////
plugins {
// Application plugin facilitates creating an executable JVM application.
id 'com.android.application'
// JaCoCo plugin provides code coverage metrics for Java code via
// integration with JaCoCo.
id 'jacoco'
// Gradle plugin for running SonarQube analysis.
id 'org.sonarqube'
// Generates an HTML dependency report. This report combines the features
// of the ASCII dependency report and those of the ASCII dependency insight
// report. For a given project, it generates a tree of the dependencies of
// every configuration, and each dependency can be clicked to show the
// insight of this dependency.
id 'project-report'
// Add support for the language Kotlin.
id 'kotlin-android'
id 'org.jetbrains.kotlin.android'
// Add support for IDE plugin.
// Useful to add custom sources and tests directories.
id 'idea'
// Gradle plugin to discover dependency updates
id 'com.github.ben-manes.versions'
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Get system components versions from properties
///////////////////////////////////////////////////////////////////////////////
println('Reading system properties.')
def javaBinariesPath, javaUnitTestBinaries, javaAndroidTestBinaries
if (gradleVersionValue >= 8.3) {
javaBinariesPath = 'compileDebugJavaWithJavac/classes/'
javaUnitTestBinaries = 'compileDebugUnitTestJavaWithJavac/classes/'
javaAndroidTestBinaries = 'compileDebugAndroidTestJavaWithJavac/classes/'
} else {
javaBinariesPath = 'classes/'
javaUnitTestBinaries = 'classes/'
javaAndroidTestBinaries = 'classes/'
}
println('Android API version: ' + androidApiVersion)
println('NDK version: ' + ndk_version)
println('Test type: ' + systemTestType)
println('ABI Filters: ' + systemAbiFilters)
println('Gradle version: ' + gradle_version)
println('AndroidX appcompat version: ' + androidX_appcompat_version)
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Set source directories for the IDE
///////////////////////////////////////////////////////////////////////////////
ext {
javaDir = file("${projectDir}/src/main/java")
kotlinDir = file("${projectDir}/src/main/java")
mobileRtDir = file("${projectDir}/MobileRT")
componentsDir = file("${projectDir}/Components")
scenesDir = file("${projectDir}/Scenes")
systemDependentDir = file("${projectDir}/System_dependent")
resourcesAssetsDir = file("${projectDir}/src/main/assets")
resourcesProdDir = file("${projectDir}/src/main/res")
unitTestsDir = file("${projectDir}/Unit_Testing")
unitTestsJavaDir = file("${projectDir}/src/test/java")
androidTestsJavaDir = file("${projectDir}/src/androidTest/java")
resourcesTestsDir = file("${projectDir}/src/test/resources")
resourcesAndroidTestsDir = file("${projectDir}/src/androidTest/resources")
}
idea.module {
sourceDirs += javaDir
sourceDirs += kotlinDir
sourceDirs += mobileRtDir
sourceDirs += componentsDir
sourceDirs += scenesDir
sourceDirs += systemDependentDir
resourceDirs += resourcesAssetsDir
resourceDirs += resourcesProdDir
getTestSources().setFrom(unitTestsDir, unitTestsJavaDir, androidTestsJavaDir)
getTestResources().setFrom(resourcesTestsDir, resourcesAndroidTestsDir)
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Add flags to Java compiler
///////////////////////////////////////////////////////////////////////////////
allprojects {
tasks.withType(JavaCompile).configureEach {
println('Adding additional flags to the compiler.')
options.setCompilerArgs([
'-Xlint:all',
'-verbose'
])
options.setFork(true)
options.setIncremental(true)
options.setFailOnError(true)
options.setVerbose(true)
options.setWarnings(true)
options.setListFiles(true)
options.setDeprecation(true)
options.setDebug(true)
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// JaCoCo plugin configuration
///////////////////////////////////////////////////////////////////////////////
jacoco {
setToolVersion "${jacoco_version}"
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// SonarQube plugin configuration
///////////////////////////////////////////////////////////////////////////////
sonar {
setAndroidVariant 'debug'
setSkipProject false
// Properties taken from: https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/
properties {
// Standard Sonar properties
property 'sonar.projectKey','TiagoMSSantos_MobileRT'
property 'sonar.projectDescription', 'Ray Tracing engine for multiple platforms'
property 'sonar.projectVersion', "${project.version}"
property 'sonar.projectBaseDir', "${project.projectDir}"
// Additional properties provided for projects that have the Java-base or Java plugin applied
property 'sonar.sourceEncoding', 'UTF-8'
property 'sonar.java.source', "${project.targetCompatibility}"
property 'sonar.java.target', "${project.targetCompatibility}"
property 'sonar.sources', 'src/main/java'
property 'sonar.tests', 'src/test/java, src/androidTest/java'
property 'sonar.java.binaries', "build/intermediates/javac/debug/${javaBinariesPath},build/tmp/kotlin-classes/debug/"
properties['sonar.java.test.binaries'] += "build/intermediates/javac/debugUnitTest/${javaUnitTestBinaries}"
properties['sonar.java.test.binaries'] += "build/intermediates/javac/debugAndroidTest/${javaAndroidTestBinaries}"
property 'sonar.junit.reportPaths', 'build/test-results/testDebugUnitTest'
// More properties
property 'sonar.inclusions', '**/src/**/*.java,**/src/**/*.kt,**/*.cpp,**/*.hpp'
property 'sonar.exclusions', '**/Unit_Testing/**,**/test**,**/**Generated**,**/third_party**,**/build**'
property 'sonar.host.url', 'https://sonarcloud.io'
property 'sonar.verbose', 'true'
// Properties not documented
property 'sonar.projectName', 'MobileRT'
property 'sonar.binaries', 'build'
property 'sonar.java.coveragePlugin', 'jacoco'
property 'sonar.kotlin.coveragePlugin', 'jacoco'
property 'sonar.androidLint.reportPaths', 'build/reports/lint-results.xml'
property 'sonar.organization', 'tiagomssantos'
property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/coverage/androidTest/debug/connected/report.xml'
property 'sonar.log.level', 'TRACE'
property 'sonar.log.level.app', 'TRACE'
property 'sonar.log.level.web', 'TRACE'
property 'sonar.log.level.ce', 'TRACE'
property 'sonar.log.level.es', 'TRACE'
property 'sonar.scm.exclusions.disabled', 'false'
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Android setup
///////////////////////////////////////////////////////////////////////////////
android {
namespace = 'puscas.mobilertapp'
testNamespace = 'puscas.mobilertapp.test'
// Set NDK version
setNdkVersion ndk_version
// Set build type for the tests
setTestBuildType systemTestType
// Setup required versions to compile
setCompileSdkVersion androidApiVersionValue >= 16? 35 : 34
setBuildToolsVersion '35.0.0'
// Setup signing configurations
signingConfigs {
Puscas {
setStoreFile file('MobileRT.jks')
setStorePassword '123456'
setKeyPassword '123456'
setKeyAlias 'Puscas'
}
}
configurations {
// Necessary to remove `listenablefuture` from dependencies:
// https://stackoverflow.com/questions/56639529/duplicate-class-com-google-common-util-concurrent-listenablefuture-found-in-modu
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
}
// Setup default configurations
defaultConfig {
// The minimum Android API version possible is 14 due to compatibility with Appcompat dependency.
setMinSdkVersion "${androidApiVersion}"
setTargetSdkVersion androidApiVersionValue >= 16? 35 : 34
setTestInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
testInstrumentationRunnerArguments = ['clearPackageData': 'true', 'disableAnalytics': 'true']
setApplicationId 'puscas.mobilertapp'
setVersionName '1.0'
setVersionCode 1
setMultiDexEnabled true
testHandleProfiling = false
testFunctionalTest = false
ndk {
abiFilters systemAbiFilters
setModuleName 'MobileRT'
}
externalNativeBuild {
cmake {
arguments '-DCMAKE_VERBOSE_MAKEFILE=ON'
}
}
javaCompileOptions {
annotationProcessorOptions {
}
}
}
// Setup native configurations
externalNativeBuild {
cmake {
setPath 'CMakeLists.txt'
setVersion '3.31.1'
}
}
// Set Java version
compileOptions {
setTargetCompatibility androidApiVersionValue >= 16? JavaVersion.VERSION_21 : JavaVersion.VERSION_17
setSourceCompatibility androidApiVersionValue >= 16? JavaVersion.VERSION_21 : JavaVersion.VERSION_17
}
kotlinOptions {
setJvmTarget androidApiVersionValue >= 16? JavaVersion.VERSION_21.toString() : JavaVersion.VERSION_17.toString()
freeCompilerArgs += [
'-opt-in=kotlin.RequiresOptIn',
'-opt-in=kotlinx.coroutines.DelicateCoroutinesApi',
]
}
// Set build types
buildTypes {
debug {
setDebuggable true
setJniDebuggable true
setTestCoverageEnabled true
setVersionNameSuffix 'd'
setRenderscriptDebuggable true
setRenderscriptOptimLevel 0
setMinifyEnabled false
setSigningConfig signingConfigs.Puscas
setShrinkResources false
ndk {
}
}
release {
setDebuggable false
setJniDebuggable false
setTestCoverageEnabled false
setVersionNameSuffix 'r'
setRenderscriptDebuggable false
setRenderscriptOptimLevel 3
setMinifyEnabled false
setSigningConfig signingConfigs.Puscas
setShrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
ndk {
}
}
}
// Setup product flavors
productFlavors {
}
packagingOptions {
resources {
// Exclude some files from the package due to conflicts from different dependencies
excludes += ['META-INF/LICENSE.md', 'META-INF/LICENSE-notice.md', 'META-INF/licenses/ASM', 'META-INF/versions/9/OSGI-INF/MANIFEST.MF']
}
}
// Configure test options
testOptions {
unitTests.all {
ignoreFailures = false
minHeapSize = '128m'
maxHeapSize = '512m'
jvmArgs '-noverify', '-ea', '-Djdk.attach.allowAttachSelf=true',
// Flags added for Java 9+ to be able to use reflection. because
// Java Platform Module System that was introduced in Java 9, has an
// implementation of strong encapsulation.
// '--add-opens {A}/{package}={B}' (If the reflecting code is in a named module, 'B' can be replaced by its name.)
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED',
'--add-opens=java.base/java.util.concurrent=ALL-UNNAMED',
'--add-opens=java.base/java.io=ALL-UNNAMED',
'--add-opens=java.base/java.nio=ALL-UNNAMED',
'--add-opens=java.logging/java.util.logging=ALL-UNNAMED',
// Allow to use reflection in private native methods:
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED'
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*']
excludeClassLoaders = ["*ClassLoader*"]
}
}
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = true
setAnimationsDisabled true
}
// Add resources directories to tests
sourceSets {
// Source files
main {
java.srcDirs += [javaDir, kotlinDir, mobileRtDir, componentsDir, scenesDir, systemDependentDir]
kotlin.srcDirs += [kotlinDir]
}
// Unit tests
test {
resources.srcDirs += ['src/test/resources']
}
// Instrumentation tests
androidTest {
manifest.srcFile 'src/androidTest/AndroidManifest.xml'
resources.srcDirs += ['src/androidTest/resources']
}
}
// adbOptions
installation {
installOptions ['-t']
}
// Setup linter options
lint {
enable 'WrongThreadInterprocedural', 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
lintConfig = file('lint.xml')
}
project.gradle.taskGraph.whenReady {
android.productFlavors.configureEach { flavor ->
// Capitalize (as Gradle is case-sensitive).
def flavorName = flavor.name.substring(0, 1).toUpperCase() + flavor.name.substring(1)
"connected${flavorName}DebugAndroidTest" {
ignoreFailures = false
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Add configurations
///////////////////////////////////////////////////////////////////////////////
configurations {
debug
release
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Merge Jacoco reports from Unit Tests and Instrumentation Tests
///////////////////////////////////////////////////////////////////////////////
tasks.register('jacocoTestReport', JacocoReport) {
dependsOn = [
'test' + systemTestType.capitalize() + 'UnitTest',
'connected' + systemTestType.capitalize() + 'AndroidTest',
]
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
// Set source files.
def sourceFiles = files(["${project.projectDir}/src/main/java/**/*"]).getFiles()
println('sourceFiles: ' + sourceFiles)
sourceDirectories.setFrom(sourceFiles)
// Set class files.
def javaClasses = fileTree(
dir: "${layout.buildDirectory.getAsFile().get().toString()}/intermediates/javac/debug/${javaBinariesPath}",
// Exclude the 'BuildConfig.class' because it is an automatically generated file.
excludes: ['**/BuildConfig.class', '**/package-info.class', '**/AgentJar.class'],
)
def kotlinClasses = fileTree(
dir: "${layout.buildDirectory.getAsFile().get().toString()}/tmp/kotlin-classes/debug/",
excludes: ['**/BuildConfig.class', '**/package-info.class', '**/AgentJar.class'],
)
def allClasses = javaClasses + kotlinClasses
println('allClasses: ' + allClasses)
classDirectories.setFrom(allClasses)
// Set execution data files to analyze.
executionData.setFrom(fileTree(
dir: "${layout.buildDirectory.getAsFile().get().toString()}",
includes: ['**/test*UnitTest.exec', '**/*coverage.ec'],
))
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Setup tests to execute in parallel
///////////////////////////////////////////////////////////////////////////////
// ?: -> binary operator: x ?: y <==> x ? x : y
tasks.withType(Test).configureEach {
maxParallelForks = Runtime.runtime.availableProcessors()
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Add third party dependencies
///////////////////////////////////////////////////////////////////////////////
dependencies {
// Dependencies for the Android application
println('Adding dependencies for MobileRT.')
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
println('Adding Android dependencies for MobileRT.')
implementation "androidx.appcompat:appcompat:${androidX_appcompat_version}"
println('Adding Java dependencies for MobileRT.')
implementation 'com.google.guava:guava:33.4.0-jre'
implementation 'net.sourceforge.streamsupport:streamsupport:1.7.4'
println('Adding dependencies for the instrumentation tests.')
androidTestImplementation "androidx.test.espresso:espresso-intents:${androidX_test_espresso_version}"
androidTestImplementation "androidx.test.ext:junit:${androidX_test_junit_version}"
println('Enabling tracing for the instrumentation tests.')
debugImplementation "androidx.test.espresso:espresso-core:${androidX_test_espresso_version}"
println('Adding dependencies for the unit tests.')
testImplementation 'org.assertj:assertj-core:3.27.2'
testImplementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
testImplementation "org.powermock:powermock-module-junit4:${powermock_version}"
testImplementation "org.powermock:powermock-api-easymock:${powermock_version}"
testImplementation 'org.easymock:easymock:5.5.0'
testImplementation 'org.springframework:spring-test:6.2.1'
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Add third party repositories
///////////////////////////////////////////////////////////////////////////////
repositories {
google()
mavenCentral()
mavenLocal()
gradlePluginPortal()
maven { url = 'https://maven.google.com' }
maven { url = 'https://mvnrepository.com' }
maven { url = 'https://dl.google.com/dl/android/maven2/' }
maven { url = 'https://plugins.gradle.org/m2/'}
maven { url = 'https://jitpack.io' }
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Ignore beta versions of dependencies in the discover of dependency updates
///////////////////////////////////////////////////////////////////////////////
def isNonStable = { String version ->
def betaKeyword = ['RC', 'CANDIDATE', 'ALPHA', 'BETA', 'M1'].any { it -> version.toUpperCase().contains(it) }
return betaKeyword
}
// https://github.com/ben-manes/gradle-versions-plugin
tasks.named('dependencyUpdates').configure {
resolutionStrategy {
componentSelection.configureEach {
if (isNonStable(it.candidate.version) && !isNonStable(it.currentVersion)) {
reject('Release candidate')
}
if (it.currentVersion.contains('native-mt') && !it.candidate.version.contains('native-mt')) {
reject('kotlinx.coroutines not the same type of version')
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////