import de.undercouch.gradle.tasks.download.Download import de.undercouch.gradle.tasks.download.Verify import groovy.io.FileType import groovy.json.JsonSlurper import groovy.xml.XmlUtil import org.apache.tools.ant.taskdefs.condition.Os import java.util.regex.Matcher import java.util.regex.Pattern buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' } } plugins { id 'de.undercouch.download' version '3.3.0' id "cz.malohlava" version "1.0.3" id "io.github.http-builder-ng.http-plugin" version "0.1.1" } allprojects { repositories { google() jcenter() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir } ext { RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0' VERSION_CODE = project.hasProperty('VERSION_CODE') ? project.getProperty('VERSION_CODE') : '0' RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' appVersionCode = Integer.valueOf(VERSION_CODE ?: 1) appVersionName = RELEASE_NUMBER ?: "1.0" } def appDir = new File(projectDir, 'apps/interface') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://build-deps.overte.org/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") task extractGvrBinaries() { doLast { def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries'); zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-audio-1.101.0.aar')).visit { element -> def fileName = element.file.toString(); if (fileName.endsWith('libgvr_audio.so') && fileName.contains('arm64-v8a')) { copy { from element.file; into gvrLibFolder } } } zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-base-1.101.0.aar')).visit { element -> def fileName = element.file.toString(); if (fileName.endsWith('libgvr.so') && fileName.contains('arm64-v8a')) { copy { from element.file; into gvrLibFolder } } } fileTree(gvrLibFolder).visit { element -> if (element.file.toString().endsWith('.so')) { copy { from element.file; into jniFolder } } } } } def generateAssetsFileList = { def assetsPath = "${appDir}/src/main/assets/" def addedByAndroidDeployQtName = "--Added-by-androiddeployqt--/" def addedByAndroidDeployQtPath = assetsPath + addedByAndroidDeployQtName def addedByAndroidDeployQt = new File(addedByAndroidDeployQtPath) if (!addedByAndroidDeployQt.exists() && !addedByAndroidDeployQt.mkdirs()) { throw new GradleScriptException("Failed to create directory " + addedByAndroidDeployQtPath, null); } def outputFilename = "/qt_cache_pregenerated_file_list" def outputFile = new File(addedByAndroidDeployQtPath + outputFilename); Map> directoryContents = new TreeMap<>(); def dir = new File(assetsPath) dir.eachFileRecurse (FileType.ANY) { file -> def name = file.path.substring(assetsPath.length()) int slashIndex = name.lastIndexOf('/') def pathName = slashIndex >= 0 ? name.substring(0, slashIndex) : "/" def fileName = slashIndex >= 0 ? name.substring(pathName.length() + 1) : name if (!fileName.isEmpty() && file.isDirectory() && !fileName.endsWith("/")) { fileName += "/" } if (!directoryContents.containsKey(pathName)) { directoryContents[pathName] = new ArrayList() } if (!fileName.isEmpty()) { directoryContents[pathName].add(fileName); } } DataOutputStream fos = new DataOutputStream(new FileOutputStream(outputFile)); for (Map.Entry> e: directoryContents.entrySet()) { def entryList = e.getValue() fos.writeInt(e.key.length()*2); // 2 bytes per char fos.writeChars(e.key); fos.writeInt(entryList.size()); for (String entry: entryList) { fos.writeInt(entry.length()*2); fos.writeChars(entry); } } fos.close(); } // Copy required Qt main libraries and required plugins based on the predefined list here // FIXME eventually we would like to use the readelf functionality to automatically detect dependencies // from our built applications and use that during the full build process. However doing so would mean // hooking existing Android build tasks since the output from the qtBundle logic adds JNI libs, asset // files and resources files and potentially modifies the AndroidManifest.xml task qtBundle { doLast { parseQtDependencies(QT5_DEPS) def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/") //def qmlImportFolder = new File("${projectDir}/app/src/main/cpp") scanQmlImports(qmlImportFolder) generateLibsXml() generateAssetsFileList() } } task setupDependencies() { // migrated to python } task cleanDependencies(type: Delete) { } def runBreakpadDumpSyms = { buildType -> gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS def objDir = new File("${appDir}/build/intermediates/cmake/${buildType}/obj/arm64-v8a") def stripDebugSymbol = "${appDir}/build/intermediates/transforms/stripDebugSymbol/${buildType}/0/lib/arm64-v8a/" def outputDir = new File(breakpadDumpSymsDir, buildType) if (!outputDir.exists()) { outputDir.mkdirs() } objDir.eachFileRecurse (FileType.FILES) { file -> if (file.name.endsWith('.so')) { def output = file.name + ".sym" def cmdArgs = [ file.toString(), stripDebugSymbol ] def result = exec { workingDir HIFI_ANDROID_PRECOMPILED + '/breakpad/bin' commandLine './dump_syms' args cmdArgs ignoreExitValue true standardOutput = new BufferedOutputStream(new FileOutputStream(new File(outputDir, output))) } } } } task runBreakpadDumpSymsDebug() { doLast { runBreakpadDumpSyms("debug"); } } task runBreakpadDumpSymsRelease() { doLast { runBreakpadDumpSyms("release"); } } task zipDumpSymsDebug(type: Zip, dependsOn: runBreakpadDumpSymsDebug) { from (new File(breakpadDumpSymsDir, "debug").absolutePath) archiveName "symbols-${RELEASE_NUMBER}-debug.zip" destinationDir(new File("${appDir}/build/tmp/")) } task zipDumpSymsRelease(type: Zip, dependsOn: runBreakpadDumpSymsRelease) { from (new File(breakpadDumpSymsDir, "release").absolutePath) archiveName "symbols-${RELEASE_NUMBER}-release.zip" destinationDir(new File("${appDir}/build/tmp/")) } task uploadBreakpadDumpSymsDebug(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsDebug) { onlyIf { System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN") } config { request.uri = System.getenv("CMAKE_BACKTRACE_URL") } post { request.uri.path = '/post' request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")] request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-debug.zip").bytes request.contentType = 'application/octet-stream' response.success { println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-debug.zip uploaded") } } } task uploadBreakpadDumpSymsRelease(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsRelease) { onlyIf { System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN") } config { request.uri = System.getenv("CMAKE_BACKTRACE_URL") } post { request.uri.path = '/post' request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")] request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-release.zip").bytes request.contentType = 'application/octet-stream' response.success { println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-release.zip uploaded") } } } // FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. // See the comment on the qtBundle task above /* // FIXME derive the path from the gradle environment def toolchain = [ version: '4.9', prefix: 'aarch64-linux-android', // FIXME derive from the host OS ndkHost: 'windows-x86_64', ] def findDependentLibrary = { String name -> def libFolders = [ new File(qmlRoot, 'lib'), new File("${HIFI_ANDROID_PRECOMPILED}/tbb/lib/release"), new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/Release"), new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/"), new File("${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/libraries"), ] } def readElfBinary = new File(android.ndkDirectory, "/toolchains/${toolchain.prefix}-${toolchain.version}/prebuilt/${toolchain.ndkHost}/bin/${toolchain.prefix}-readelf${EXEC_SUFFIX}") def getDependencies = { File elfBinary -> Set result = [] Queue pending = new LinkedList<>() pending.add(elfBinary) Set scanned = [] Pattern p = ~/.*\(NEEDED\).*Shared library: \[(.*\.so)\]/ while (!pending.isEmpty()) { File current = pending.remove() if (scanned.contains(current)) { continue } scanned.add(current) def command = "${readElfBinary} -d -W ${current.absolutePath}" captureOutput(command).split('[\r\n]').each { line -> Matcher m = p.matcher(line) if (!m.matches()) { return } def libName = m.group(1) def file = new File(qmlRoot, "lib/${libName}") if (file.exists()) { result.add(file) pending.add(file) } } } return result } task testElf (dependsOn: 'externalNativeBuildDebug') { doLast { def appLibraries = new HashSet() def qtDependencies = new HashSet() externalNativeBuildDebug.nativeBuildConfigurationsJsons.each { File file -> def json = new JsonSlurper().parse(file) json.libraries.each { node -> def outputFile = new File(node.value.output) if (outputFile.canonicalPath.startsWith(projectDir.canonicalPath)) { appLibraries.add(outputFile) } } } appLibraries.each { File file -> println getDependencies(file) } } } */