enrecipes/platforms/android/app/build.gradle
2020-12-01 12:41:41 +05:30

984 lines
36 KiB
Groovy

/*
* Script builds apk in release or debug mode
* To run:
* gradle assembleRelease -Prelease (release mode)
* gradle assembleDebug (debug mode -> default)
* Options:
* -Prelease //this flag will run build in release mode
* -PksPath=[path_to_keystore_file]
* -PksPassword=[password_for_keystore_file]
* -Palias=[alias_to_use_from_keystore_file]
* -Ppassword=[password_for_alias]
*
* -PtargetSdk=[target_sdk]
* -PbuildToolsVersion=[build_tools_version]
* -PcompileSdk=[compile_sdk_version]
* -PandroidXLegacy=[androidx_legacy_version]
* -PandroidXAppCompat=[androidx_appcompat_version]
* -PandroidXMaterial=[androidx_material_version]
* -PappPath=[app_path]
* -PappResourcesPath=[app_resources_path]
*/
import groovy.io.FileType
import groovy.json.JsonSlurper
import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.security.MessageDigest
import org.gradle.internal.logging.text.StyledTextOutputFactory
import static org.gradle.internal.logging.text.StyledTextOutput.Style
apply plugin: "com.android.application"
apply from: "gradle-helpers/BuildToolTask.gradle"
apply from: "gradle-helpers/CustomExecutionLogger.gradle"
apply from: "gradle-helpers/AnalyticsCollector.gradle"
def enableKotlin = (project.hasProperty("useKotlin") && project.useKotlin == "true")
if (enableKotlin) {
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
}
def onlyX86 = project.hasProperty("onlyX86")
if (onlyX86) {
outLogger.withStyle(Style.Info).println "OnlyX86 build triggered."
}
//common
def BUILD_TOOLS_PATH = "$rootDir/build-tools"
def PASSED_TYPINGS_PATH = System.getenv("TNS_TYPESCRIPT_DECLARATIONS_PATH")
def TYPINGS_PATH = "$BUILD_TOOLS_PATH/typings"
if (PASSED_TYPINGS_PATH != null) {
TYPINGS_PATH = PASSED_TYPINGS_PATH
}
def PACKAGE_JSON = "package.json"
//static binding generator
def SBG_JAVA_DEPENDENCIES = "sbg-java-dependencies.txt"
def SBG_INPUT_FILE = "sbg-input-file.txt"
def SBG_OUTPUT_FILE = "sbg-output-file.txt"
def SBG_JS_PARSED_FILES = "sbg-js-parsed-files.txt"
def SBG_BINDINGS_NAME = "sbg-bindings.txt"
def SBG_INTERFACE_NAMES = "sbg-interface-names.txt"
def INPUT_JS_DIR = "$projectDir/src/main/assets/app"
def OUTPUT_JAVA_DIR = "$projectDir/src/main/java"
//metadata generator
def MDG_OUTPUT_DIR = "mdg-output-dir.txt"
def MDG_JAVA_DEPENDENCIES = "mdg-java-dependencies.txt"
def METADATA_OUT_PATH = "$projectDir/src/main/assets/metadata"
// paths to jar libraries
def pluginsJarLibraries = new LinkedList<String>()
def allJarLibraries = new LinkedList<String>()
def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "1.3.41" }
def computeCompileSdkVersion = { -> project.hasProperty("compileSdk") ? compileSdk : 29 }
def computeTargetSdkVersion = { -> project.hasProperty("targetSdk") ? targetSdk : 29 }
def computeBuildToolsVersion = { ->
project.hasProperty("buildToolsVersion") ? buildToolsVersion : "29.0.2"
}
def enableAnalytics = (project.hasProperty("gatherAnalyticsData") && project.gatherAnalyticsData == "true")
def enableVerboseMDG = project.gradle.startParameter.logLevel.name() == 'DEBUG'
def analyticsFilePath = "$rootDir/analytics/build-statistics.json"
def analyticsCollector = project.ext.AnalyticsCollector.withOutputPath(analyticsFilePath)
if (enableAnalytics) {
analyticsCollector.markUseKotlinPropertyInApp(enableKotlin)
analyticsCollector.writeAnalyticsFile()
}
project.ext.selectedBuildType = project.hasProperty("release") ? "release" : "debug"
buildscript {
// we have moved these initializations here as we need getAppResourcesPath to search for buildscript.gradle files,
// but the buildscript is executed before executing anything else from the gradle file
def initialize = { ->
// the build script will not work with previous versions of the CLI (3.1 or earlier)
def dependenciesJson = file("$rootDir/dependencies.json")
if (!dependenciesJson.exists()) {
throw new BuildCancelledException("""
'dependencies.json' file not found. Check whether the NativeScript CLI has prepared the project beforehand,
and that your NativeScript version is 3.3, or a more recent one. To build an android project with the current
version of the {N} CLI install a previous version of the runtime package - 'tns platform add android@3.2'.
""")
}
project.ext.extractedDependenciesDir = "${project.buildDir}/exploded-dependencies"
project.ext.cleanupAllJarsTimestamp = "${project.buildDir}/cleanupAllJars.timestamp"
project.ext.extractAllJarsTimestamp = "${project.buildDir}/extractAllJars.timestamp"
project.ext.nativescriptDependencies = new JsonSlurper().parseText(dependenciesJson.text)
project.ext.PLATFORMS_ANDROID = "platforms/android"
project.ext.USER_PROJECT_ROOT = "$rootDir/../.."
project.ext.outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger")
project.ext.getAppPath = { ->
def relativePathToApp = "app"
def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json")
def nsConfig
if (nsConfigFile.exists()) {
nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8"))
}
if (project.hasProperty("appPath")) {
// when appPath is passed through -PappPath=/path/to/app
// the path could be relative or absolute - either case will work
relativePathToApp = appPath
} else if (nsConfig != null && nsConfig.appPath != null) {
relativePathToApp = nsConfig.appPath
}
project.ext.appPath = java.nio.file.Paths.get(USER_PROJECT_ROOT).resolve(relativePathToApp).toAbsolutePath()
return project.ext.appPath
}
project.ext.getAppResourcesPath = { ->
def relativePathToAppResources
def absolutePathToAppResources
def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json")
def nsConfig
if (nsConfigFile.exists()) {
nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8"))
}
if (project.hasProperty("appResourcesPath")) {
// when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources
// the path could be relative or absolute - either case will work
relativePathToAppResources = appResourcesPath
absolutePathToAppResources = java.nio.file.Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath()
} else if (nsConfig != null && nsConfig.appResourcesPath != null) {
relativePathToAppResources = nsConfig.appResourcesPath
absolutePathToAppResources = java.nio.file.Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath()
} else {
absolutePathToAppResources = "${getAppPath()}/App_Resources"
}
project.ext.appResourcesPath = absolutePathToAppResources
return absolutePathToAppResources
}
}
def applyBuildScriptConfigurations = { ->
def absolutePathToAppResources = getAppResourcesPath()
def pathToBuildScriptGradle = "$absolutePathToAppResources/Android/buildscript.gradle"
def buildScriptGradle = file(pathToBuildScriptGradle)
if (buildScriptGradle.exists()) {
outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from ${buildScriptGradle}"
apply from: pathToBuildScriptGradle, to: buildscript
}
nativescriptDependencies.each { dep ->
def pathToPluginBuildScriptGradle = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/buildscript.gradle"
def pluginBuildScriptGradle = file(pathToPluginBuildScriptGradle)
if (pluginBuildScriptGradle.exists()) {
outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from dependency ${pluginBuildScriptGradle}"
apply from: pathToPluginBuildScriptGradle, to: buildscript
}
}
}
initialize()
applyBuildScriptConfigurations()
}
////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// CONFIGURATIONS ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
def applyBeforePluginGradleConfiguration = { ->
def appResourcesPath = getAppResourcesPath()
def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle"
def beforePluginGradle = file(pathToBeforePluginGradle)
if (beforePluginGradle.exists()) {
outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${beforePluginGradle}"
apply from: pathToBeforePluginGradle
}
}
def applyAppGradleConfiguration = { ->
def appResourcesPath = getAppResourcesPath()
def pathToAppGradle = "$appResourcesPath/Android/app.gradle"
def appGradle = file(pathToAppGradle)
if (appGradle.exists()) {
outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${appGradle}"
apply from: pathToAppGradle
} else {
outLogger.withStyle(Style.Info).println "\t + couldn't load user-defined configuration from ${appGradle}. File doesn't exist."
}
}
def applyPluginGradleConfigurations = { ->
nativescriptDependencies.each { dep ->
def includeGradlePath = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/include.gradle"
if (file(includeGradlePath).exists()) {
apply from: includeGradlePath
}
}
}
def getAppIdentifier = { packageJsonMap ->
def appIdentifier = ""
if (packageJsonMap && packageJsonMap.nativescript) {
appIdentifier = packageJsonMap.nativescript.id
if (!(appIdentifier instanceof String)) {
appIdentifier = appIdentifier.android
}
}
return appIdentifier
}
def setAppIdentifier = { ->
outLogger.withStyle(Style.SuccessHeader).println "\t + setting applicationId"
File packageJsonFile = new File("$USER_PROJECT_ROOT/$PACKAGE_JSON")
if (packageJsonFile.exists()) {
def content = packageJsonFile.getText("UTF-8")
def jsonSlurper = new JsonSlurper()
def packageJsonMap = jsonSlurper.parseText(content)
def appIdentifier = getAppIdentifier(packageJsonMap)
if (appIdentifier) {
project.ext.nsApplicationIdentifier = appIdentifier
android.defaultConfig.applicationId = appIdentifier
}
}
}
android {
if (enableKotlin) {
kotlinOptions {
jvmTarget = '1.8'
}
}
compileSdkVersion computeCompileSdkVersion()
buildToolsVersion computeBuildToolsVersion()
defaultConfig {
def manifest = new XmlSlurper().parse(file(android.sourceSets.main.manifest.srcFile))
def minSdkVer = manifest."uses-sdk"."@android:minSdkVersion".text() ?: 17
minSdkVersion minSdkVer
targetSdkVersion computeTargetSdkVersion()
ndk {
if (onlyX86) {
abiFilters 'x86'
} else {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
dexOptions {
jumboMode = true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets.main {
jniLibs.srcDirs = ["$projectDir/libs/jni", "$projectDir/snapshot-build/build/ndk-build/libs"]
}
signingConfigs {
release {
if (project.hasProperty("release")) {
if (project.hasProperty("ksPath") &&
project.hasProperty("ksPassword") &&
project.hasProperty("alias") &&
project.hasProperty("password")) {
storeFile file(ksPath)
storePassword ksPassword
keyAlias alias
keyPassword password
}
}
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
setAppIdentifier()
applyBeforePluginGradleConfiguration()
applyPluginGradleConfigurations()
applyAppGradleConfiguration()
def initializeMergedAssetsOutputPath = { ->
android.applicationVariants.all { variant ->
if (variant.buildType.name == project.selectedBuildType) {
def task
if (variant.metaClass.respondsTo(variant, "getMergeAssetsProvider")) {
def provider = variant.getMergeAssetsProvider()
task = provider.get();
} else {
// fallback for older android gradle plugin versions
task = variant.getMergeAssets()
}
for (File file : task.getOutputs().getFiles()) {
if (!file.getPath().contains("${File.separator}incremental${File.separator}")) {
project.ext.mergedAssetsOutputPath = file.getPath()
break;
}
}
}
}
}
initializeMergedAssetsOutputPath()
}
def externalRuntimeExists = !findProject(':runtime').is(null)
def pluginDependencies
repositories {
// used for local *.AAR files
pluginDependencies = nativescriptDependencies.collect {
"$rootDir/${it.directory}/$PLATFORMS_ANDROID"
}
// some plugins may have their android dependencies in a /libs subdirectory
pluginDependencies.addAll(nativescriptDependencies.collect {
"$rootDir/${it.directory}/$PLATFORMS_ANDROID/libs"
})
if (!externalRuntimeExists) {
pluginDependencies.add("libs/runtime-libs")
}
def appResourcesPath = getAppResourcesPath()
def localAppResourcesLibraries = "$appResourcesPath/Android/libs"
pluginDependencies.add(localAppResourcesLibraries)
if (pluginDependencies.size() > 0) {
flatDir {
dirs pluginDependencies
}
}
mavenCentral()
}
dependencies {
def androidXLegacyVersion = "1.0.0"
if (project.hasProperty("androidXLegacy")) {
androidXLegacyVersion = androidXLegacy
}
def androidXAppCompatVersion = "1.1.0"
if (project.hasProperty("androidXAppCompat")) {
androidXAppCompatVersion = androidXAppCompat
}
def androidXMaterialVersion = "1.1.0"
if (project.hasProperty("androidXMaterial")) {
androidXMaterialVersion = androidXMaterial
}
def androidXExifInterfaceVersion = "1.2.0"
if (project.hasProperty("androidXExifInterface")) {
androidxExifInterfaceVersion = androidXExifInterface
}
def androidXViewPagerVersion = "1.0.0"
if (project.hasProperty("androidXViewPager")) {
androidXViewPagerVersion = androidXViewPager
}
def androidXFragmentVersion = "1.2.5"
if (project.hasProperty("androidXFragment")) {
androidXFragmentVersion = androidXFragment
}
def androidXTransitionVersion = "1.3.1"
if (project.hasProperty("androidXTransition")) {
androidXTransitionVersion = androidXTransition
}
outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.legacy:legacy-support-v4:$androidXLegacyVersion"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.legacy:legacy-support-v4:$androidXLegacyVersion"
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"
implementation "com.google.android.material:material:$androidXMaterialVersion"
implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion"
implementation "androidx.viewpager2:viewpager2:$androidXViewPagerVersion"
implementation "androidx.fragment:fragment:$androidXFragmentVersion"
implementation "androidx.transition:transition:$androidXTransitionVersion"
def useV8Symbols = false
def appPackageJsonFile = file("${getAppPath()}/$PACKAGE_JSON")
if (appPackageJsonFile.exists()) {
def appPackageJson = new JsonSlurper().parseText(appPackageJsonFile.text)
useV8Symbols = appPackageJson.android && appPackageJson.android.useV8Symbols
}
if (!useV8Symbols) {
// check whether any of the dependencies require v8 symbols
useV8Symbols = nativescriptDependencies.any {
def packageJsonFile = file("$rootDir/${it.directory}/$PACKAGE_JSON")
def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
return packageJson.nativescript && packageJson.nativescript.useV8Symbols
}
}
if (!externalRuntimeExists) {
def runtime = "nativescript-optimized-with-inspector"
if (project.gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') }) {
runtime = "nativescript-optimized"
}
if (useV8Symbols) {
runtime = "nativescript-regular"
}
outLogger.withStyle(Style.SuccessHeader).println "\t + adding nativescript runtime package dependency: $runtime"
project.dependencies.add("implementation", [name: runtime, ext: "aar"])
} else {
implementation project(':runtime')
}
def kotlinVersion = computeKotlinVersion()
if (enableKotlin) {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
}
}
////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// CONFIGURATION PHASE //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
task addDependenciesFromNativeScriptPlugins {
nativescriptDependencies.each { dep ->
def aarFiles = fileTree(dir: file("$rootDir/${dep.directory}/$PLATFORMS_ANDROID"), include: ["**/*.aar"])
aarFiles.each { aarFile ->
def length = aarFile.name.length() - 4
def fileName = aarFile.name[0..<length]
outLogger.withStyle(Style.SuccessHeader).println "\t + adding aar plugin dependency: " + aarFile.getAbsolutePath()
project.dependencies.add("implementation", [name: fileName, ext: "aar"])
}
def jarFiles = fileTree(dir: file("$rootDir/${dep.directory}/$PLATFORMS_ANDROID"), include: ["**/*.jar"])
jarFiles.each { jarFile ->
def jarFileAbsolutePath = jarFile.getAbsolutePath()
outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath"
pluginsJarLibraries.add(jarFile.getAbsolutePath())
}
project.dependencies.add("implementation", jarFiles)
}
}
task addDependenciesFromAppResourcesLibraries {
def appResourcesPath = getAppResourcesPath()
def appResourcesLibraries = file("$appResourcesPath/Android/libs")
if (appResourcesLibraries.exists()) {
def aarFiles = fileTree(dir: appResourcesLibraries, include: ["**/*.aar"])
aarFiles.each { aarFile ->
def length = aarFile.name.length() - 4
def fileName = aarFile.name[0..<length]
outLogger.withStyle(Style.SuccessHeader).println "\t + adding aar library dependency: " + aarFile.getAbsolutePath()
project.dependencies.add("implementation", [name: fileName, ext: "aar"])
}
def jarFiles = fileTree(dir: appResourcesLibraries, include: ["**/*.jar"])
jarFiles.each { jarFile ->
def jarFileAbsolutePath = jarFile.getAbsolutePath()
outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath"
pluginsJarLibraries.add(jarFile.getAbsolutePath())
}
project.dependencies.add("implementation", jarFiles)
}
}
if (failOnCompilationWarningsEnabled()) {
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:all' << "-Werror"
options.deprecation = true
}
}
tasks.whenTaskAdded({ org.gradle.api.DefaultTask currentTask ->
if (currentTask =~ /generate.+BuildConfig/) {
currentTask.finalizedBy(extractAllJars)
extractAllJars.finalizedBy(collectAllJars)
}
if (currentTask =~ /compile.+JavaWithJavac/) {
currentTask.dependsOn(runSbg)
currentTask.finalizedBy(buildMetadata)
}
if (currentTask =~ /merge.*Assets/) {
currentTask.shouldRunAfter(buildMetadata)
}
if (currentTask =~ /assemble.*Debug/ || currentTask =~ /assemble.*Release/) {
currentTask.finalizedBy("validateAppIdMatch")
}
})
////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// EXECUTUION PHASE /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
task runSbg(type: BuildToolTask) {
dependsOn "collectAllJars"
if (!findProject(':static-binding-generator').is(null)) {
dependsOn ':static-binding-generator:jar'
}
outputs.dir("$OUTPUT_JAVA_DIR/com/tns/gen")
inputs.dir(INPUT_JS_DIR)
inputs.dir(extractedDependenciesDir)
workingDir "$BUILD_TOOLS_PATH"
main "-jar"
def paramz = new ArrayList<String>()
paramz.add("static-binding-generator.jar")
if (failOnCompilationWarningsEnabled()) {
paramz.add("-show-deprecation-warnings")
}
setOutputs outLogger
args paramz
doFirst {
new File("$OUTPUT_JAVA_DIR/com/tns/gen").deleteDir()
}
}
def failOnCompilationWarningsEnabled() {
return project.hasProperty("failOnCompilationWarnings") && (failOnCompilationWarnings || failOnCompilationWarnings.toBoolean())
}
def explodeAar(File compileDependency, File outputDir) {
logger.info("explodeAar: Extracting ${compileDependency.path} -> ${outputDir.path}")
if (compileDependency.name.endsWith(".aar")) {
java.util.jar.JarFile jar = new java.util.jar.JarFile(compileDependency)
Enumeration enumEntries = jar.entries()
while (enumEntries.hasMoreElements()) {
java.util.jar.JarEntry file = (java.util.jar.JarEntry) enumEntries.nextElement()
if (file.name.endsWith(".jar")) {
def targetFile = new File(outputDir, file.name)
InputStream inputStream = jar.getInputStream(file)
new File(targetFile.parent).mkdirs()
Files.copy(inputStream, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
if (file.isDirectory()) {
continue
}
}
jar.close()
} else if (compileDependency.name.endsWith(".jar")) {
copy {
from compileDependency.absolutePath
into outputDir
}
}
}
def md5(String string) {
MessageDigest digest = MessageDigest.getInstance("MD5")
digest.update(string.bytes)
return new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
}
class WorkerTask extends DefaultTask {
@Inject
WorkerExecutor getWorkerExecutor() {
throw new UnsupportedOperationException()
}
}
class EmptyRunnable implements Runnable {
void run() {
}
}
def getMergedAssetsOutputPath() {
if (!project.hasProperty("mergedAssetsOutputPath")) {
// mergedAssetsOutputPath not found fallback to the default value for android gradle plugin 3.5.1
project.ext.mergedAssetsOutputPath = "$projectDir/build/intermediates/merged_assets/" + project.selectedBuildType + "/out"
}
return project.ext.mergedAssetsOutputPath
}
// Discover all jars and dynamically create tasks for the extraction of each of them
project.ext.allJars = []
afterEvaluate { project ->
def buildType = project.selectedBuildType == "release" ? "Release" : "Debug"
def jars = []
def artifactType = Attribute.of('artifactType', String)
configurations.all { config ->
if (config.name.toLowerCase().endsWith("${buildType}RuntimeClasspath".toLowerCase())) {
config.incoming.artifactView {
attributes {
it.attribute(artifactType, 'jar')
}
}.artifacts.each {
processJar(it.file, jars)
}
def projectDependencies = config.getAllDependencies().withType(ProjectDependency)
def dependentProjects = projectDependencies*.dependencyProject
dependentProjects.findAll {
// if there's a project dependency search for its result jar file in the build/intermediates/runtime_library_classes folder
// this is the output folder in gradle 5.1.1, but it can be changed in the future versions of gradle
def jarDir = new File("${it.getBuildDir()}/intermediates/runtime_library_classes/${buildType.toLowerCase()}")
if (jarDir.exists()) {
jarDir.eachFileRecurse(FileType.FILES) { file ->
if (file.path.endsWith(".jar")) {
processJar(file, jars)
}
}
} else {
outLogger.withStyle(Style.Info).println "WARNING: Folder ${jarDir.path} does not exists, the dependent project's classes won't be included in the metadata"
}
}
}
}
}
def processJar(File jar, jars) {
if (!jars.contains(jar)) {
jars.add(jar)
def destDir = md5(jar.path)
def outputDir = new File(Paths.get(extractedDependenciesDir, destDir).normalize().toString())
def taskName = "extract_${jar.name}_to_${destDir}"
logger.debug("Creating dynamic task ${taskName}")
// Add discovered jars as dependencies of cleanupAllJars.
// This is cruicial for cloud builds because they are different
// on each incremental build (as each time the gradle user home
// directory is a randomly generated string)
cleanupAllJars.inputs.files jar
task "${taskName}"(type: WorkerTask) {
dependsOn cleanupAllJars
extractAllJars.dependsOn it
// This dependency seems redundant but probably due to some Gradle issue with workers,
// without it `runSbg` sporadically starts before all extraction tasks have finished and
// fails due to missing JARs
runSbg.dependsOn it
inputs.files jar
outputs.dir outputDir
doLast {
// Runing in parallel no longer seems to bring any benefit.
// It mattered only when we were extracting JARs from AARs.
// To try it simply remove the following comments.
// workerExecutor.submit(EmptyRunnable.class) {
explodeAar(jar, outputDir)
// }
}
}
project.ext.allJars.add([file: jar, outputDir: outputDir])
}
}
task cleanupAllJars {
// We depend on the list of libs directories that might contain aar or jar files
// and on the list of all discovered jars
inputs.files(pluginDependencies)
outputs.files cleanupAllJarsTimestamp
doLast {
def allDests = project.ext.allJars*.outputDir*.name
def dir = new File(extractedDependenciesDir)
if (dir.exists()) {
dir.eachDir {
// An old directory which is no longer a dependency (e.g. orphaned by a deleted plugin)
if (!allDests.contains(it.name)) {
logger.info("Task cleanupAllJars: Deleting orphaned ${it.path}")
org.apache.commons.io.FileUtils.deleteDirectory(it)
}
}
}
new File(cleanupAllJarsTimestamp).write ""
}
}
// Placeholder task which depends on all dynamically generated extraction tasks
task extractAllJars {
dependsOn cleanupAllJars
outputs.files extractAllJarsTimestamp
doLast {
new File(cleanupAllJarsTimestamp).write ""
}
}
task collectAllJars {
dependsOn extractAllJars
description "gathers all paths to jar dependencies before building metadata with them"
def sdkPath = android.sdkDirectory.getAbsolutePath()
def androidJar = sdkPath + "/platforms/" + android.compileSdkVersion + "/android.jar"
doFirst {
def allJarPaths = new LinkedList<String>()
allJarPaths.add(androidJar)
allJarPaths.addAll(pluginsJarLibraries)
def ft = fileTree(dir: extractedDependenciesDir, include: "**/*.jar")
ft.each { currentJarFile ->
allJarPaths.add(currentJarFile.getAbsolutePath())
}
new File("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES").withWriter { out ->
allJarPaths.each { out.println it }
}
new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriter { out ->
allJarPaths.each {
if (it.endsWith(".jar")) {
out.println it
}
}
}
new File("$BUILD_TOOLS_PATH/$SBG_INPUT_FILE").withWriter { out ->
out.println INPUT_JS_DIR
}
new File("$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE").withWriter { out ->
out.println OUTPUT_JAVA_DIR
}
allJarLibraries.addAll(allJarPaths)
}
}
task copyMetadataFilters(type: Copy) {
from "$rootDir/whitelist.mdg", "$rootDir/blacklist.mdg"
into "$BUILD_TOOLS_PATH"
}
task copyMetadata {
doLast {
copy {
from "$projectDir/src/main/assets/metadata"
into getMergedAssetsOutputPath() + "/metadata"
}
}
}
task buildMetadata(type: BuildToolTask) {
if (!findProject(':android-metadata-generator').is(null)) {
dependsOn ':android-metadata-generator:jar'
}
dependsOn copyMetadataFilters
// As some external gradle plugins can reorder the execution order of the tasks it may happen that buildMetadata is executed after merge{Debug/Release}Assets
// in that case the metadata won't be included in the result apk and it will crash, so to avoid this we are adding the copyMetadata task which will manually copy
// the metadata files in the merge assets folder and they will be added to the result apk
// The next line is added to avoid adding another copyData implementation from the firebase plugin - https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/3943bb9147f43c41599e801d026378eba93d3f3a/publish/scripts/installer.js#L1105
//buildMetadata.finalizedBy(copyMetadata)
finalizedBy copyMetadata
description "builds metadata with provided jar dependencies"
inputs.files("$MDG_JAVA_DEPENDENCIES")
// make MDG aware of whitelist.mdg and blacklist.mdg files
inputs.files(project.fileTree(dir: "$rootDir", include: "**/*.mdg"))
def classesDir = "$buildDir/intermediates/javac"
inputs.dir(classesDir)
def kotlinClassesDir = "$buildDir/tmp/kotlin-classes"
if (file(kotlinClassesDir).exists()) {
inputs.dir(kotlinClassesDir)
}
outputs.files("$METADATA_OUT_PATH/treeNodeStream.dat", "$METADATA_OUT_PATH/treeStringsStream.dat", "$METADATA_OUT_PATH/treeValueStream.dat")
workingDir "$BUILD_TOOLS_PATH"
main "-jar"
doFirst {
// get compiled classes to pass to metadata generator
// these need to be called after the classes have compiled
assert file(classesDir).exists()
new File(getMergedAssetsOutputPath() + "/metadata").deleteDir()
def classesSubDirs = new File(classesDir).listFiles()
def selectedBuildType = project.ext.selectedBuildType
def generatedClasses = new LinkedList<String>()
for (File subDir : classesSubDirs) {
if (subDir.getName().equals(selectedBuildType)) {
generatedClasses.add(subDir.getAbsolutePath())
}
}
if (file(kotlinClassesDir).exists()) {
def kotlinClassesSubDirs = new File(kotlinClassesDir).listFiles()
for (File subDir : kotlinClassesSubDirs) {
if (subDir.getName() == selectedBuildType) {
generatedClasses.add(subDir.getAbsolutePath())
}
}
}
new File("$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR").withWriter { out ->
out.println "$METADATA_OUT_PATH"
}
new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriterAppend { out ->
generatedClasses.each { out.println it }
}
setOutputs outLogger
def paramz = new ArrayList<String>()
paramz.add("android-metadata-generator.jar")
if(enableAnalytics){
paramz.add("analyticsFilePath=$analyticsFilePath")
}
if(enableVerboseMDG){
paramz.add("verbose")
}
args paramz.toArray()
}
}
task generateTypescriptDefinitions(type: BuildToolTask) {
if (!findProject(':dts-generator').is(null)) {
dependsOn ':dts-generator:jar'
}
def paramz = new ArrayList<String>()
def includeDirs = ["com.android.support", "/platforms/" + android.compileSdkVersion]
workingDir "$BUILD_TOOLS_PATH"
main "-jar"
doFirst {
delete "$TYPINGS_PATH"
paramz.add("dts-generator.jar")
paramz.add("-input")
for (String jarPath : allJarLibraries) {
// don't generate typings for runtime jars and classes
if (shouldIncludeDirForTypings(jarPath, includeDirs)) {
paramz.add(jarPath)
}
}
paramz.add("-output")
paramz.add("$TYPINGS_PATH")
new File("$TYPINGS_PATH").mkdirs()
logger.info("Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', ''))
outLogger.withStyle(Style.SuccessHeader).println "Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', '')
setOutputs outLogger
args paramz.toArray()
}
}
generateTypescriptDefinitions.onlyIf {
(project.hasProperty("generateTypings") && Boolean.parseBoolean(project.generateTypings)) || PASSED_TYPINGS_PATH != null
}
collectAllJars.finalizedBy(generateTypescriptDefinitions)
static def shouldIncludeDirForTypings(path, includeDirs) {
for (String p : includeDirs) {
if (path.indexOf(p) > -1) {
return true
}
}
return false
}
task copyTypings {
doLast {
outLogger.withStyle(Style.Info).println "Copied generated typings to application root level. Make sure to import android.d.ts in reference.d.ts"
copy {
from "$TYPINGS_PATH"
into "$USER_PROJECT_ROOT"
}
}
}
copyTypings.onlyIf { generateTypescriptDefinitions.didWork }
generateTypescriptDefinitions.finalizedBy(copyTypings)
task validateAppIdMatch {
doLast {
def lineSeparator = System.getProperty("line.separator")
if (project.hasProperty("nsApplicationIdentifier") && !project.hasProperty("release")) {
if (project.nsApplicationIdentifier != android.defaultConfig.applicationId) {
def errorMessage = "${lineSeparator}WARNING: The Application identifier is different from the one inside \"package.json\" file.$lineSeparator" +
"NativeScript CLI might not work properly.$lineSeparator" +
"Remove applicationId from app.gradle and update the \"nativescript.id\" in package.json.$lineSeparator" +
"Actual: ${android.defaultConfig.applicationId}$lineSeparator" +
"Expected(from \"package.json\"): ${project.nsApplicationIdentifier}$lineSeparator"
logger.error(errorMessage)
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////// OPTIONAL TASKS //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
//////// custom clean ///////////
task cleanSbg(type: Delete) {
delete "$BUILD_TOOLS_PATH/$SBG_JS_PARSED_FILES",
"$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES",
"$BUILD_TOOLS_PATH/$SBG_INTERFACE_NAMES",
"$BUILD_TOOLS_PATH/$SBG_BINDINGS_NAME",
"$BUILD_TOOLS_PATH/$SBG_INPUT_FILE",
"$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE",
"$OUTPUT_JAVA_DIR/com/tns/gen"
}
task cleanMdg(type: Delete) {
delete "$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR",
"$BUILD_TOOLS_PATH/whitelist.mdg",
"$BUILD_TOOLS_PATH/blacklist.mdg",
"$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES",
"$METADATA_OUT_PATH"
}
cleanSbg.dependsOn(cleanMdg)
clean.dependsOn(cleanSbg)