@CacheableTask abstract class WriteFileTask extends DefaultTask { @Input abstract Property getContent() @OutputFile abstract RegularFileProperty getTarget() @TaskAction void run() { def file = target.get().asFile file.parentFile.mkdirs() def text = content.get() if (!file.exists() || text != file.text) file.text = text } } class Utils implements Plugin { final def BUILD_TYPES = ['Debug', 'Release'].asImmutable() final def BUILD_TYPES_REG_EXP = BUILD_TYPES.join('|') final def ABI_CODES = ["Arm7": "armeabi-v7a", "Arm8": "arm64-v8a", "X86_64": "x86_64", "X86": "x86"].asImmutable() final def CPU_CODES = ["Arm7": "arm", "Arm8": "arm64", "X86_64": "x64", "X86": "x86"].asImmutable() def project @Inject Utils(Project project) { this.project = project } void apply(Project project) { project.extensions.create('utils', Utils) } void addTask(String prefix, Closure taskClosure) { forEachBuildVariant { String game, String arch, String buildType -> def taskName = "${prefix}${game}${arch}${buildType}" taskClosure(taskName, buildType, arch, game) } } void forEachBuildVariant(Closure callback) { project.android.productFlavors.each { flavor -> project.rootProject.ext.targetArchs.each { arch -> BUILD_TYPES.each { buildType -> callback(flavor.name.capitalize(), arch, buildType) } } } } } def generateGnArgsContent(String buildType, String arch) { def content = 'target_os="android"\n' content += 'target_cpu="' + utils.CPU_CODES[arch] + '"\n' content += "is_debug=${buildType != 'Release'}\n" content += 'ndk="' + android.ndkDirectory + '"\n' content += "ndk_api=${rootProject.ext.minSdk}\n" return content } def getOutDir(String buildType) { return "${project.buildDir}/gn_out/${buildType.toLowerCase()}" } def getAssetsDir(String buildType) { return "${project.buildDir}/gn_out/${buildType.toLowerCase()}/assets" } def getJniLibsDir(String buildType) { return "${project.buildDir}/gn_out/jniLibs/${buildType.toLowerCase()}" } def getGnTargetFor(String game) { def outStr = '' android.productFlavors.find { flavor -> if (flavor.name.capitalize() == game) { outStr = flavor.ext.gnTarget return true } } return outStr } apply plugin: 'com.android.application' apply plugin: Utils rootProject.ext.targetArchs.each { arch -> assert utils.ABI_CODES.containsKey(arch) assert utils.CPU_CODES.containsKey(arch) } android { compileSdk rootProject.ext.compileSdk ndkVersion rootProject.ext.ndkVersion defaultConfig { minSdk rootProject.ext.minSdk targetSdk rootProject.ext.targetSdk ndk { abiFilters = [] rootProject.ext.targetArchs.each { arch -> abiFilters.add(utils.ABI_CODES[arch]) } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } flavorDimensions 'game' productFlavors { helloWorld { dimension 'game' applicationId 'com.kaliber.helloworld' resValue "string", "provider_name", "${applicationId}.fileprovider" resValue "string", "app_name", "Kaliber Hello World" resValue "string", "interstitial_ad_unit_id", "" ext { gnTarget = "hello_world" } } demo { dimension 'game' applicationId 'com.kaliber.woom' resValue "string", "provider_name", "${applicationId}.fileprovider" resValue "string", "app_name", "Kaliber Demo" resValue "string", "interstitial_ad_unit_id", "ca-app-pub-1321063817979967/8373182022" ext { gnTarget = "demo" } } } sourceSets { main { java.srcDirs += ['../../../src/engine/platform/java/com/kaliber/base'] utils.BUILD_TYPES.each { buildType -> "${buildType.toLowerCase()}" { assets.srcDirs = [getAssetsDir(buildType)] } } } utils.BUILD_TYPES.each { buildType -> "${buildType.toLowerCase()}" { jniLibs.srcDirs = [getJniLibsDir(buildType)] } } } namespace "com.kaliber.base" } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.google.android.gms:play-services-ads:22.0.0' } utils.addTask('generateGnArgsFor') { String taskName, String buildType, String arch, String game -> task(taskName, type: WriteFileTask) { content = generateGnArgsContent(buildType, arch) target = project.layout.file(provider { new File("${getOutDir(buildType)}/${utils.ABI_CODES[arch]}", 'args.gn') }) } } utils.addTask('runGnFor') { String taskName, String buildType, String arch, String game -> task(taskName, type: Exec) { dependsOn "generateGnArgsFor${game}${arch}${buildType}" executable rootProject.ext.gn args '--fail-on-unused-args', 'gen', "${getOutDir(buildType)}/${utils.ABI_CODES[arch]}" inputs.file(new File("${getOutDir(buildType)}/${utils.ABI_CODES[arch]}", 'args.gn')) outputs.file(new File("${getOutDir(buildType)}/${utils.ABI_CODES[arch]}", 'build.ninja')) } } utils.addTask('runNinjaFor') { String taskName, String buildType, String arch, String game -> task(taskName, type: Exec) { dependsOn "runGnFor${game}${arch}${buildType}" executable rootProject.ext.ninja args '-C', "${getOutDir(buildType)}/${utils.ABI_CODES[arch]}", getGnTargetFor(game) outputs.upToDateWhen { false } } } utils.addTask('copyAssetsFor') { String taskName, String buildType, String arch, String game -> task(taskName, type: Copy) { dependsOn "runNinjaFor${game}${arch}${buildType}" from "${getOutDir(buildType)}/${utils.ABI_CODES[arch]}/assets" into getAssetsDir(buildType) } } utils.addTask('copyJniLibsFor') { String taskName, String buildType, String arch, String game -> task(taskName, type: Copy) { dependsOn "runNinjaFor${game}${arch}${buildType}" from("${getOutDir(buildType)}/${utils.ABI_CODES[arch]}") { include "lib${getGnTargetFor(game)}.so" rename "lib${getGnTargetFor(game)}.so", "libkaliber.so" } into "${getJniLibsDir(buildType)}/${utils.ABI_CODES[arch]}" } } tasks.configureEach { task -> def match = task.name =~ /^merge(\w+)(${utils.BUILD_TYPES_REG_EXP})JniLibFolders$/ if (match) { rootProject.ext.targetArchs.each { arch -> task.dependsOn "copyJniLibsFor${match.group(1)}${arch}${match.group(2)}" } return } match = task.name =~ /^merge(\w+)(${utils.BUILD_TYPES_REG_EXP})Assets$/ if (match) { rootProject.ext.targetArchs.each { arch -> task.dependsOn "copyAssetsFor${match.group(1)}${arch}${match.group(2)}" } return } match = task.name =~ /^lintVitalAnalyze(\w+)(${utils.BUILD_TYPES_REG_EXP})$/ if (match) { rootProject.ext.targetArchs.each { arch -> task.dependsOn "copyAssetsFor${match.group(1)}${arch}${match.group(2)}" } return } }