TiagoMSSantos/MobileRT

View on GitHub
app/build.gradle

Summary

Maintainability
Test Coverage
///////////////////////////////////////////////////////////////////////////////
// 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')
            }
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////