11/*
2- * Script builds apk in release or debug mode
3- * To run:
4- * gradle assembleRelease -Prelease (release mode)
5- * gradle assembleDebug (debug mode -> default)
6- * Options:
7- * -Prelease //this flag will run build in release mode
8- * -PksPath=[path_to_keystore_file]
9- * -PksPassword=[password_for_keystore_file]
10- * -Palias=[alias_to_use_from_keystore_file]
11- * -Ppassword=[password_for_alias]
2+ * Script builds apk in release or debug mode
3+ * To run:
4+ * gradle assembleRelease -Prelease (release mode)
5+ * gradle assembleDebug (debug mode -> default)
6+ * Options:
7+ * -Prelease //this flag will run build in release mode
8+ * -PksPath=[path_to_keystore_file]
9+ * -PksPassword=[password_for_keystore_file]
10+ * -Palias=[alias_to_use_from_keystore_file]
11+ * -Ppassword=[password_for_alias]
1212*
13- * -PtargetSdk=[target_sdk]
14- * -PbuildToolsVersion=[build_tools_version]
15- * -PsupportVersion=[support_version]
16- * -PcompileSdk=[compile_sdk_version]
13+ * -PtargetSdk=[target_sdk]
14+ * -PbuildToolsVersion=[build_tools_version]
15+ * -PsupportVersion=[support_version]
16+ * -PcompileSdk=[compile_sdk_version]
1717*/
1818
1919import groovy.json.JsonSlurper
20+ import java.nio.file.Files
2021import java.nio.file.Paths
22+ import java.nio.file.StandardCopyOption
2123import java.util.regex.Matcher
2224import java.util.regex.Pattern
23-
25+ import java.security.MessageDigest
26+ import javax.inject.Inject
2427apply plugin : " com.android.application"
2528
2629def onlyX86 = project. hasProperty(" onlyX86" )
@@ -80,6 +83,8 @@ version of the {N} CLI install a previous version of the runtime package - 'tns
8083 }
8184
8285 project. ext. extractedDependenciesDir = " ${ project.buildDir} /exploded-dependencies"
86+ project. ext. cleanupAllJarsTimestamp = " ${ project.buildDir} /cleanupAllJars.timestamp"
87+ project. ext. extractAllJarsTimestamp = " ${ project.buildDir} /extractAllJars.timestamp"
8388 project. ext. nativescriptDependencies = new JsonSlurper (). parseText(dependenciesJson. text)
8489 project. ext. PLATFORMS_ANDROID = " platforms/android"
8590 project. ext. USER_PROJECT_ROOT = " $rootDir /../.."
@@ -257,10 +262,11 @@ android {
257262}
258263
259264def externalRuntimeExists = ! findProject(' :runtime' ). is(null )
265+ def pluginDependencies
260266
261267repositories {
262268 // used for local *.AAR files
263- def pluginDependencies = nativescriptDependencies. collect {
269+ pluginDependencies = nativescriptDependencies. collect {
264270 " $rootDir /${ it.directory} /$PLATFORMS_ANDROID "
265271 }
266272
@@ -416,11 +422,14 @@ tasks.whenTaskAdded({ org.gradle.api.DefaultTask currentTask ->
416422// //////////////////////////////////////////////////////////////////////////////////
417423
418424task runSbg (type : JavaExec ) {
425+ dependsOn " collectAllJars"
419426 if (! findProject(' :static-binding-generator' ). is(null )) {
420427 dependsOn ' :static-binding-generator:jar'
421428 }
422429
430+ outputs. dir(OUTPUT_JAVA_DIR )
423431 inputs. dir(INPUT_JS_DIR )
432+ inputs. dir(extractedDependenciesDir)
424433
425434 workingDir " $BUILD_TOOLS_PATH "
426435 main " -jar"
@@ -438,22 +447,19 @@ task ensureMetadataOutDir {
438447 }
439448}
440449
441- def explodeAar (File compileDependency , String outputDir ) {
450+ def explodeAar (File compileDependency , File outputDir ) {
451+ logger. info(" explodeAar: Extracting ${ compileDependency.path} -> ${ outputDir.path} " )
452+
442453 if (compileDependency. name. endsWith(" .aar" )) {
443454 java.util.jar.JarFile jar = new java.util.jar.JarFile (compileDependency)
444455 Enumeration enumEntries = jar. entries()
445456 while (enumEntries. hasMoreElements()) {
446457 java.util.jar.JarEntry file = (java.util.jar.JarEntry ) enumEntries. nextElement()
447458 if (file. name. endsWith(" .jar" )) {
448- def f = new File (outputDir, file. name)
449- new File (f. parent). mkdirs()
450- InputStream is = jar. getInputStream(file)
451- FileOutputStream fos = new FileOutputStream (f)
452- while (is. available() > 0 ) {
453- fos. write(is. read())
454- }
455- fos. close()
456- is. close()
459+ def targetFile = new File (outputDir, file. name)
460+ InputStream inputStream = jar. getInputStream(file)
461+ new File (targetFile. parent). mkdirs()
462+ Files . copy(inputStream, targetFile. toPath(), StandardCopyOption . REPLACE_EXISTING );
457463 }
458464 if (file. isDirectory()) {
459465 continue
@@ -468,40 +474,111 @@ def explodeAar(File compileDependency, String outputDir) {
468474 }
469475}
470476
471- task extractAllJars {
472- outputs. dir extractedDependenciesDir
477+ def md5 (String string ) {
478+ MessageDigest digest = MessageDigest . getInstance(" MD5" ) ;
479+ digest. update(string. bytes);
480+ return new BigInteger (1 , digest. digest()). toString(16 ). padLeft(32 , ' 0' );
481+ }
473482
474- doLast {
475- def buildType = project. selectedBuildType == " release" ? " Release" : " Debug"
476- def iter = []
477- Pattern pattern = Pattern . compile(" ^(.+)${ buildType} CompileClasspath\$ " )
478- def artifactType = Attribute . of(' artifactType' , String )
479- configurations. all { config ->
480- Matcher matcher = pattern. matcher(config. name)
481- if (matcher. find() || config. name == " ${ buildType.toLowerCase()} CompileClasspath" ) {
482- config. incoming. artifactView {
483- attributes {
484- it. attribute(artifactType, ' jar' )
485- }
486- }. artifacts. each {
487- if (! iter. contains(it. file)) {
488- iter. push(it. file)
483+ class WorkerTask extends DefaultTask {
484+ @Inject
485+ WorkerExecutor getWorkerExecutor () {
486+ throw new UnsupportedOperationException ()
487+ }
488+ }
489+
490+ class EmptyRunnable implements Runnable {
491+ void run () {
492+ }
493+ }
494+
495+ // Discover all jars and dynamically create tasks for the extraction of each of them
496+ def allJars = []
497+ afterEvaluate { project ->
498+ def buildType = project. selectedBuildType == " release" ? " Release" : " Debug"
499+ def jars = []
500+ Pattern pattern = Pattern . compile(" ^(.+)${ buildType} CompileClasspath\$ " )
501+ def artifactType = Attribute . of(' artifactType' , String )
502+ configurations. all { config ->
503+ Matcher matcher = pattern. matcher(config. name)
504+ if (matcher. find() || config. name == " ${ buildType.toLowerCase()} CompileClasspath" ) {
505+ config. incoming. artifactView {
506+ attributes {
507+ it. attribute(artifactType, ' jar' )
508+ }
509+ }. artifacts. each {
510+ def jar = it. file;
511+ if (! jars. contains(jar)) {
512+ jars. push(jar)
513+ def destDir = md5(jar. path);
514+ def outputDir = new File (Paths . get(extractedDependenciesDir, destDir). normalize(). toString())
515+
516+ def taskName = " extract_${ jar.name} _to_${ destDir} "
517+ logger. debug(" Creating dynamic task ${ taskName} " )
518+ task " ${ taskName} " (type : WorkerTask ) {
519+ dependsOn cleanupAllJars
520+ extractAllJars. dependsOn it
521+
522+ // This dependency seems redundant but probably due to some Gradle issue with workers,
523+ // without it `runSbg` sporadically starts before all extraction tasks have finished and
524+ // fails due to missing JARs
525+ runSbg. dependsOn it
526+
527+ inputs. files jar
528+ outputs. dir outputDir
529+
530+ doLast {
531+ // Runing in parallel no longer seems to bring any benefit.
532+ // It mattered only when we were extracting JARs from AARs.
533+ // To try it simply remove the following comments.
534+ // workerExecutor.submit(EmptyRunnable.class) {
535+ explodeAar(jar, outputDir)
536+ // }
537+ }
489538 }
539+ allJars. push([file : jar, outputDir : outputDir])
490540 }
491541 }
492542 }
543+ }
544+ }
493545
494- def dependencyCounter = 0
495- iter. each {
496- def nextDependency = it
497- def outputDir = java.nio.file.Paths . get(extractedDependenciesDir, " " + dependencyCounter). normalize(). toString()
498- explodeAar(nextDependency, outputDir)
499- dependencyCounter++
546+ task cleanupAllJars {
547+ // Ideally we would depend on the list of all discovered jars, but we cannot discover them before
548+ // the execution phase of the build begins, so instead we depend on the list of libs directories that might
549+ // contain aar or jar files.
550+ inputs. files(pluginDependencies)
551+ outputs. files cleanupAllJarsTimestamp
552+
553+ doLast {
554+ def allDests = allJars* . outputDir* . name;
555+ def dir = new File (extractedDependenciesDir)
556+ if (dir. exists()) {
557+ dir. eachDir {
558+ // An old directory which is no longer a dependency (e.g. orphaned by a deleted plugin)
559+ if (! allDests. contains(it. name)) {
560+ logger. info(" Task cleanupAllJars: Deleting orphaned ${ it.path} " )
561+ org.apache.commons.io.FileUtils . deleteDirectory(it)
562+ }
563+ }
500564 }
565+ new File (cleanupAllJarsTimestamp). write " "
566+ }
567+ }
568+
569+
570+ // Placeholder task which depends on all dynamically generated extraction tasks
571+ task extractAllJars {
572+ dependsOn cleanupAllJars
573+ outputs. files extractAllJarsTimestamp
574+
575+ doLast {
576+ new File (cleanupAllJarsTimestamp). write " "
501577 }
502578}
503579
504580task collectAllJars {
581+ dependsOn extractAllJars
505582 description " gathers all paths to jar dependencies before building metadata with them"
506583
507584 def sdkPath = android. sdkDirectory. getAbsolutePath()
0 commit comments