From 61ea0de74280aaafeb9fb3e9fd20e22a13e92a6e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Dec 2018 13:28:53 -0800 Subject: [PATCH] Update build files based on move --- .gitignore | 3 + CMakeLists.txt | 4 +- android/apps/interface/CMakeLists.txt | 17 +- android/apps/interface/build.gradle | 56 ++- android/build.gradle | 375 +----------------- android/build_android.sh | 11 +- android/containerized_build.sh | 11 +- android/docker/update.txt | 13 + android/libraries/qt/build.gradle | 22 + .../libraries/qt/src/main/AndroidManifest.xml | 2 + .../java/io/highfidelity/utils/HifiUtils.java | 69 ++++ .../qt5/android/bindings/QtActivity.java | 0 .../android/bindings/QtActivityLoader.java | 0 .../qt5/android/bindings/QtApplication.java | 0 android/settings.gradle | 6 +- hifi_android.py | 62 ++- hifi_utils.py | 4 - hifi_vcpkg.py | 18 + prebuild.py | 14 +- 19 files changed, 274 insertions(+), 413 deletions(-) create mode 100644 android/docker/update.txt create mode 100644 android/libraries/qt/build.gradle create mode 100644 android/libraries/qt/src/main/AndroidManifest.xml create mode 100644 android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java rename android/{apps/interface => libraries/qt}/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java (100%) rename android/{apps/interface => libraries/qt}/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java (100%) rename android/{apps/interface => libraries/qt}/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java (100%) diff --git a/.gitignore b/.gitignore index f5605d7090..3f58e46b69 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,13 @@ Makefile # Android Studio *.iml +*.class local.properties android/gradle* android/.gradle android/**/src/main/jniLibs android/**/libs +android/**/bin android/**/src/main/res/values/libs.xml android/**/src/main/assets android/**/gradle* @@ -102,3 +104,4 @@ tools/unity-avatar-exporter/Logs tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings tools/unity-avatar-exporter/Temp + diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e5dbe935a..d0a2e57dd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ target_python() if (HIFI_ANDROID ) execute_process( - COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android --build-root ${CMAKE_BINARY_DIR} + COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) else() @@ -174,7 +174,7 @@ set_packaging_parameters() # FIXME hack to work on the proper Android toolchain if (ANDROID) - add_subdirectory(android/app) + add_subdirectory(android/apps/${HIFI_ANDROID_APP}) return() endif() diff --git a/android/apps/interface/CMakeLists.txt b/android/apps/interface/CMakeLists.txt index 19dce330c1..500d555915 100644 --- a/android/apps/interface/CMakeLists.txt +++ b/android/apps/interface/CMakeLists.txt @@ -4,10 +4,10 @@ link_hifi_libraries(shared task networking gl gpu qml image fbx hfm render-utils target_opengl() target_bullet() -set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface") +set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../interface") add_subdirectory("${INTERFACE_DIR}" "libraries/interface") include_directories("${INTERFACE_DIR}/src") -set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/hifiCodec") +set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/hifiCodec") add_subdirectory("${HIFI_CODEC_PLUGIN_DIR}" "libraries/hifiCodecPlugin") target_link_libraries(native-lib android log m interface) @@ -15,16 +15,3 @@ target_link_libraries(native-lib android log m interface) set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/") target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src") target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui) - -# finished libraries -# core -> qt -# networking -> openssl, tbb -# fbx -> draco -# physics -> bullet -# entities-renderer -> polyvox - -# unfinished libraries -# image -> nvtt (doesn't look good, but can be made optional) -# script-engine -> quazip (probably not required for the android client) - - diff --git a/android/apps/interface/build.gradle b/android/apps/interface/build.gradle index e3c6989baf..0c23496faa 100644 --- a/android/apps/interface/build.gradle +++ b/android/apps/interface/build.gradle @@ -1,9 +1,41 @@ import org.apache.tools.ant.taskdefs.condition.Os +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task renameHifiACTaskDebug() { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/debug/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} +task renameHifiACTaskRelease(type: Copy) { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/release/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} + apply plugin: 'com.android.application' android { - compileSdkVersion 26 + compileSdkVersion 28 //buildToolsVersion '27.0.3' def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1) @@ -12,24 +44,24 @@ android { defaultConfig { applicationId "io.highfidelity.hifiinterface" minSdkVersion 24 - targetSdkVersion 26 + targetSdkVersion 28 versionCode appVersionCode versionName appVersionName ndk { abiFilters 'arm64-v8a' } externalNativeBuild { cmake { arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=interface', '-DANDROID_PLATFORM=android-24', '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared', - '-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake', - '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, '-DSTABLE_BUILD=' + STABLE_BUILD, '-DDISABLE_QML=OFF', '-DDISABLE_KTX_CACHE=OFF', '-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF'); + targets = ['native-lib'] } } signingConfigs { @@ -72,7 +104,7 @@ android { externalNativeBuild { cmake { - path '../../CMakeLists.txt' + path '../../../CMakeLists.txt' } } @@ -82,6 +114,7 @@ android { variant.externalNativeBuildTasks.each { task -> variant.mergeResources.dependsOn(task) if (Os.isFamily(Os.FAMILY_UNIX)) { + // FIXME def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first() def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first() def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first() @@ -97,7 +130,7 @@ android { // Copy the compiled resources generated by the external native build copy { - from new File(projectDir, "../../interface/compiledResources") + from new File(projectDir, "../../../interface/compiledResources") into outputDir duplicatesStrategy DuplicatesStrategy.INCLUDE eachFile { details -> @@ -108,7 +141,7 @@ android { // Copy the scripts directory copy { - from new File(projectDir, "../../scripts") + from new File(projectDir, "../../../scripts") into new File(outputDir, "scripts") duplicatesStrategy DuplicatesStrategy.INCLUDE eachFile { details-> @@ -123,12 +156,6 @@ android { assetList.each { file -> out.println(file) } } } - - variant.outputs.all { - if (RELEASE_NUMBER != '0') { - outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" - } - } } } @@ -157,5 +184,6 @@ dependencies { api 'com.sothree.slidinguppanel:library:3.4.0' - implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + implementation project(':qt') } diff --git a/android/build.gradle b/android/build.gradle index 8d03b9f6b3..11c702130c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -42,378 +42,13 @@ ext { 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' : '' - QT5_DEPS = [ - 'Qt5Concurrent', - 'Qt5Core', - 'Qt5Gui', - 'Qt5Multimedia', - 'Qt5Network', - 'Qt5OpenGL', - 'Qt5Qml', - 'Qt5Quick', - 'Qt5QuickControls2', - 'Qt5QuickTemplates2', - 'Qt5Script', - 'Qt5ScriptTools', - 'Qt5Svg', - 'Qt5WebChannel', - 'Qt5WebSockets', - 'Qt5Widgets', - 'Qt5XmlPatterns', - // Android specific - 'Qt5AndroidExtras', - 'Qt5WebView', - ] } -def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) -def appDir = new File(projectDir, 'app') +def appDir = new File(projectDir, 'apps/interface') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") -def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz' -def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df' -def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' -if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' - qtChecksum='c83cc477c08a892e00c71764dca051a0' - qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' -} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' - qtChecksum='0582191cc55431aa4f660848a542883e' - qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT' -} - -def packages = [ - qt: [ - file: qtFile, - versionId: qtVersionId, - checksum: qtChecksum, - ], - bullet: [ - file: 'bullet-2.88_armv8-libcpp.tgz', - versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg', - checksum: '81642779ccb110f8c7338e8739ac38a0', - ], - draco: [ - file: 'draco_armv8-libcpp.tgz', - versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8', - checksum: '617a80d213a5ec69fbfa21a1f2f738cd', - ], - glad: [ - file: 'glad_armv8-libcpp.zip', - versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY', - checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449', - ], - gvr: [ - file: 'gvrsdk_v1.101.0.tgz', - versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r', - checksum: '57fd02baa069176ba18597a29b6b4fc7', - ], - nvtt: [ - file: 'nvtt_armv8-libcpp.zip', - versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO', - checksum: 'eb46d0b683e66987190ed124aabf8910', - sharedLibFolder: 'lib', - includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'], - ], - openssl: [ - file: 'openssl-1.1.0g_armv8.tgz', - versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW', - checksum: 'cabb681fbccd79594f65fcc266e02f32', - ], - polyvox: [ - file: 'polyvox_armv8-libcpp.tgz', - versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92', - checksum: 'dba88b3a098747af4bb169e9eb9af57e', - sharedLibFolder: 'lib', - includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], - ], - tbb: [ - file: 'tbb-2018_U1_armv8_libcpp.tgz', - versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB', - checksum: '20768f298f53b195e71b414b0ae240c4', - sharedLibFolder: 'lib/release', - includeLibs: ['libtbb.so', 'libtbbmalloc.so'], - ], - hifiAC: [ - baseUrl: 'http://s3.amazonaws.com/hifi-public/dependencies/', - file: 'codecSDK-android_armv8-2.0.zip', - checksum: '1cbef929675818fc64c4101b72f84a6a' - ], - etc2comp: [ - file: 'etc2comp-patched-armv8-libcpp.tgz', - versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU', - checksum: '14b02795d774457a33bbc60e00a786bc' - ], - breakpad: [ - file: 'breakpad.tgz', - versionId: '8VrYXz7oyc.QBxNia0BVJOUBvrFO61jI', - checksum: 'ddcb23df336b08017042ba4786db1d9e', - sharedLibFolder: 'lib', - includeLibs: ['libbreakpad_client.a'] - ] -] - -def options = [ - files: new TreeSet(), - features: new HashSet(), - permissions: new HashSet() -] - -def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') - -def captureOutput = { String command, List commandArgs -> - def result - new ByteArrayOutputStream().withStream { os -> - def execResult = exec { - executable = command - args = commandArgs - standardOutput = os - errorOutput = new ByteArrayOutputStream() - } - result = os.toString() - } - return result; -} - -def relativize = { File root, File absolute -> - def relativeURI = root.toURI().relativize(absolute.toURI()) - return new File(relativeURI.toString()) -} - -def scanQmlImports = { File qmlRootPath -> - def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX) - if (!qmlImportCommandFile.exists()) { - throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString()) - } - - def command = qmlImportCommandFile.absolutePath - def args = [ - '-rootPath', qmlRootPath.absolutePath, - '-importPath', "${qmlRoot.absolutePath}/qml" - ] - - def commandResult = captureOutput(command, args) - new JsonSlurper().parseText(commandResult).each { - if (!it.containsKey('path')) { - println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" - return - } - def file = new File(it.path) - // Ignore non-existent files - if (!file.exists()) { - return - } - // Ignore files in the import path - if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) { - return - } - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { - options.files.add(it) - }) - } - } -} - -def parseQtDependencies = { List qtLibs -> - qtLibs.each({ - def libFile = new File(qmlRoot, "lib/lib${it}.so") - options.files.add(libFile) - - def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml") - if (!libFile.exists()) return - if (!androidDeps.exists()) return - - new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node -> - switch (node.name()) { - case 'lib': - case 'bundled': - def relativeFilename = node.@file.toString() - - // Special case, since this is handled by qmlimportscanner instead - if (relativeFilename.startsWith('qml')) - return - - def file = new File(qmlRoot, relativeFilename) - - if (!file.exists()) - return - - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { options.files.add(it) }) - } - break - - - case 'jar': - if (node.@bundling == "1") { - def jar = new File(qmlRoot, node.@file.toString()) - if (!jar.exists()) { - throw new GradleException('Unable to find required JAR ' + jar.path) - } - options.files.add(jar) - } - break - - case 'permission': - options.permissions.add(node.@name) - break - - case 'feature': - options.features.add(node.@name) - break - - default: - throw new GradleException('Unhandled Android Dependency node ' + node.name()) - } - } - }) -} - -def generateLibsXml = { - def libDestinationDirectory = jniFolder - def jarDestinationDirectory = new File(appDir, 'libs') - def assetDestinationDirectory = new File(appDir, 'src/main/assets/--Added-by-androiddeployqt--'); - def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') - def libPrefix = 'lib' + File.separator - def jarPrefix = 'jar' + File.separator - - def xmlParser = new XmlParser() - def libsXmlRoot = xmlParser.parseText('') - def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) - def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) - def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) - - options.files.each { - def sourceFile = it - if (!sourceFile.exists()) { - throw new GradleException("Unable to find dependency file " + sourceFile.toString()) - } - - def relativePath = relativize( qmlRoot, sourceFile ).toString() - def destinationFile - if (relativePath.endsWith('.so')) { - def garbledFileName - if (relativePath.startsWith(libPrefix)) { - garbledFileName = relativePath.substring(libPrefix.size()) - Pattern p = ~/lib(Qt5.*).so/ - Matcher m = p.matcher(garbledFileName) - assert m.matches() - def libName = m.group(1) - xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) - } else { - garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) - xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) - } - destinationFile = new File(libDestinationDirectory, garbledFileName) - } else if (relativePath.startsWith('jar')) { - destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) - } else { - xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("--Added-by-androiddeployqt--/${relativePath}:${relativePath}".replace(File.separator, '/')) - destinationFile = new File(assetDestinationDirectory, relativePath) - } - - copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } - assert destinationFile.exists() && destinationFile.isFile() - } - def xml = XmlUtil.serialize(libsXmlRoot) - new FileWriter(libsXmlFile).withPrintWriter { writer -> - writer.write(xml) - } -} - -task downloadDependencies { - doLast { - packages.each { entry -> - def filename = entry.value['file']; - def dependencyBaseUrl = entry.value['baseUrl'] - def url = (dependencyBaseUrl?.trim() ? dependencyBaseUrl : baseUrl) + filename; - if (entry.value.containsKey('versionId')) { - url = url + '?versionId=' + entry.value['versionId'] - } - download { - src url - dest new File(baseFolder, filename) - onlyIfNewer true - } - } - } -} - -task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; } -task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyBreakpad(type: Verify) { def p = packages['breakpad']; src new File(baseFolder, p['file']); checksum p['checksum'] } - -task verifyDependencyDownloads(dependsOn: downloadDependencies) { } -verifyDependencyDownloads.dependsOn verifyQt -verifyDependencyDownloads.dependsOn verifyBullet -verifyDependencyDownloads.dependsOn verifyDraco -verifyDependencyDownloads.dependsOn verifyGvr -verifyDependencyDownloads.dependsOn verifyOpenSSL -verifyDependencyDownloads.dependsOn verifyPolyvox -verifyDependencyDownloads.dependsOn verifyTBB -verifyDependencyDownloads.dependsOn verifyHifiAC -verifyDependencyDownloads.dependsOn verifyEtc2Comp -verifyDependencyDownloads.dependsOn verifyBreakpad - -task extractDependencies(dependsOn: verifyDependencyDownloads) { - doLast { - packages.each { entry -> - def folder = entry.key - def filename = entry.value['file'] - def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename) - def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder) - def fileTree; - if (filename.endsWith('zip')) { - fileTree = zipTree(localFile) - } else { - fileTree = tarTree(resources.gzip(localFile)) - } - copy { - from fileTree - into localFolder - } - } - } -} - -// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task -task copyDependencies() { - doLast { - packages.each { entry -> - def packageName = entry.key - def currentPackage = entry.value; - if (currentPackage.containsKey('sharedLibFolder')) { - def localFolder = new File(baseFolder, packageName + '/' + currentPackage['sharedLibFolder']) - def tree = fileTree(localFolder); - if (currentPackage.containsKey('includeLibs')) { - currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec } - } - tree.visit { element -> - if (!element.file.isDirectory()) { - println "Copying " + element.file + " to " + jniFolder - copy { from element.file; into jniFolder } - } - } - } - } - } -} - task extractGvrBinaries() { doLast { def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries'); @@ -500,13 +135,11 @@ task qtBundle { } } -task setupDependencies(dependsOn: [copyDependencies, extractGvrBinaries, qtBundle]) { } +task setupDependencies() { + // migrated to python +} task cleanDependencies(type: Delete) { - delete HIFI_ANDROID_PRECOMPILED - delete 'app/src/main/jniLibs/arm64-v8a' - delete 'app/src/main/assets/--Added-by-androiddeployqt--' - delete 'app/src/main/res/values/libs.xml' } def runBreakpadDumpSyms = { buildType -> diff --git a/android/build_android.sh b/android/build_android.sh index 189e6099a8..9c68b8969b 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -1,4 +1,11 @@ #!/usr/bin/env bash set -xeuo pipefail -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} setupDependencies -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} app:${ANDROID_BUILD_TARGET} \ No newline at end of file +./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} + +# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK +OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_APP}-${ANDROID_BUILD_DIR}.apk +# This is the APK name requested by Jenkins +TARGET_APK=./${ANDROID_APK_NAME} +# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk" +cp ${OUTPUT_APK} ${TARGET_APK} + diff --git a/android/containerized_build.sh b/android/containerized_build.sh index e5ec895146..42118a8e38 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -5,12 +5,21 @@ DOCKER_IMAGE_NAME="hifi_androidbuild" docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/Dockerfile docker +# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION +# So make sure we use VERSION_CODE consistently +if [-z "$VERSION_CODE"]; then + export VERSION_CODE=$VERSION +fi + docker run \ --rm \ - --security-opt seccomp:unconfined \ + --security-opt seccomp:unconfined \ -v "${WORKSPACE}":/home/jenkins/hifi \ + -v /home/jenkins/.gradle:/home/jenkins/.gradle \ -e RELEASE_NUMBER \ -e RELEASE_TYPE \ + -e ANDROID_APP \ + -e ANDROID_APK_NAME \ -e ANDROID_BUILD_TARGET \ -e ANDROID_BUILD_DIR \ -e CMAKE_BACKTRACE_URL \ diff --git a/android/docker/update.txt b/android/docker/update.txt new file mode 100644 index 0000000000..a12c215a06 --- /dev/null +++ b/android/docker/update.txt @@ -0,0 +1,13 @@ +git fetch +git checkout feature/quest_move_interface +export VERSION_CODE=1 +export RELEASE_NUMBER=1 +export RELEASE_TYPE=DEV +export ANDROID_APP=interface +touch ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE=/home/jenkins/keystore.jks > ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE_PASSWORD=password >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_ALIAS=key0 >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_PASSWORD=password >> ~/.gradle/gradle.properties +./build_android.sh +cp ./apps/${ANDROID_APP}/build/outputs/apk/release/${ANDROID_APP}-release.apk ${ANDROID_APP}.apk \ No newline at end of file diff --git a/android/libraries/qt/build.gradle b/android/libraries/qt/build.gradle new file mode 100644 index 0000000000..e6141f4cdf --- /dev/null +++ b/android/libraries/qt/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + api 'com.google.guava:guava:23.0' +} diff --git a/android/libraries/qt/src/main/AndroidManifest.xml b/android/libraries/qt/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c6638c09e8 --- /dev/null +++ b/android/libraries/qt/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java new file mode 100644 index 0000000000..e8e9f04d9f --- /dev/null +++ b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java @@ -0,0 +1,69 @@ + +package io.highfidelity.utils; + +import android.content.res.AssetManager; + +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.LinkedList; + +public class HifiUtils { + + private static LinkedList readAssetLines(AssetManager assetManager, String asset) throws IOException { + LinkedList assets = new LinkedList<>(); + InputStream is = assetManager.open(asset); + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line; + while ((line=in.readLine()) != null) { + assets.add(line); + } + in.close(); + return assets; + } + + private static void copyAsset(AssetManager assetManager, String asset, String destFileName) throws IOException { + try (InputStream is = assetManager.open(asset)) { + try (OutputStream os = Files.asByteSink(new File(destFileName)).openStream()) { + ByteStreams.copy(is, os); + } + } + } + + public static void upackAssets(AssetManager assetManager, String destDir) { + try { + if (!destDir.endsWith("/")) + destDir = destDir + "/"; + LinkedList assets = readAssetLines(assetManager, "cache_assets.txt"); + String dateStamp = assets.poll(); + String dateStampFilename = destDir + dateStamp; + File dateStampFile = new File(dateStampFilename); + if (dateStampFile.exists()) { + return; + } + for (String fileToCopy : assets) { + String destFileName = destDir + fileToCopy; + { + File destFile = new File(destFileName); + File destFolder = destFile.getParentFile(); + if (!destFolder.exists()) { + destFolder.mkdirs(); + } + if (destFile.exists()) { + destFile.delete(); + } + } + copyAsset(assetManager, fileToCopy, destFileName); + } + Files.write("touch".getBytes(), dateStampFile); + } catch (IOException e){ + throw new RuntimeException(e); + } + } +} diff --git a/android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java similarity index 100% rename from android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java diff --git a/android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java similarity index 100% rename from android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java diff --git a/android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java similarity index 100% rename from android/apps/interface/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java diff --git a/android/settings.gradle b/android/settings.gradle index e7b4def49c..40b5eb44bf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,5 @@ -include ':app' +include ':qt' +project(':qt').projectDir = new File(settingsDir, 'libraries/qt') + +include ':interface' +project(':interface').projectDir = new File(settingsDir, 'apps/interface') diff --git a/hifi_android.py b/hifi_android.py index e3944cda9a..308ad2a151 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -6,6 +6,7 @@ import re import shutil import xml.etree.ElementTree as ET import functools +import zipfile print = functools.partial(print, flush=True) @@ -163,6 +164,31 @@ def copyAndroidLibs(packagePath, appPath): print("Copying {}".format(lib)) shutil.copy(sourceFile, destFile) + gvrLibFolder = os.path.join(packagePath, 'gvr/gvr-android-sdk-1.101.0/libraries') + audioSoOut = os.path.join(gvrLibFolder, 'libgvr_audio.so') + if not os.path.isfile(audioSoOut): + audioAar = os.path.join(gvrLibFolder, 'sdk-audio-1.101.0.aar') + with zipfile.ZipFile(audioAar) as z: + with z.open('jni/arm64-v8a/libgvr_audio.so') as f: + with open(audioSoOut, 'wb') as of: + shutil.copyfileobj(f, of) + + audioSoOut2 = os.path.join(jniPath, 'libgvr_audio.so') + if not os.path.isfile(audioSoOut2): + shutil.copy(audioSoOut, audioSoOut2) + + baseSoOut = os.path.join(gvrLibFolder, 'libgvr.so') + if not os.path.isfile(baseSoOut): + baseAar = os.path.join(gvrLibFolder, 'sdk-base-1.101.0.aar') + with zipfile.ZipFile(baseAar) as z: + with z.open('jni/arm64-v8a/libgvr.so') as f: + with open(baseSoOut, 'wb') as of: + shutil.copyfileobj(f, of) + + baseSoOut2 = os.path.join(jniPath, 'libgvr.so') + if not os.path.isfile(baseSoOut2): + shutil.copy(baseSoOut, baseSoOut2) + class QtPackager: def __init__(self, appPath, qtRootPath): self.appPath = appPath @@ -170,6 +196,7 @@ class QtPackager: self.jniPath = os.path.join(self.appPath, 'src/main/jniLibs/arm64-v8a') self.assetPath = os.path.join(self.appPath, 'src/main/assets') self.qtAssetPath = os.path.join(self.assetPath, '--Added-by-androiddeployqt--') + self.qtAssetCacheList = os.path.join(self.qtAssetPath, 'qt_cache_pregenerated_file_list') # Jars go into the qt library self.jarPath = os.path.realpath(os.path.join(self.appPath, '../../libraries/qt/libs')) self.xmlFile = os.path.join(self.appPath, 'src/main/res/values/libs.xml') @@ -277,10 +304,43 @@ class QtPackager: tree = ET.ElementTree(libsXmlRoot) tree.write(self.xmlFile, 'UTF-8', True) + def generateAssetsFileList(self): + print("Implement asset file list") + # outputFilename = os.path.join(self.qtAssetPath, "qt_cache_pregenerated_file_list") + # fileList = hifi_utils.recursiveFileList(self.qtAssetPath) + # fileMap = {} + # for fileName in fileList: + # relativeFileName = os.path.relpath(fileName, self.assetPath) + # dirName, localFileName = os.path.split(relativeFileName) + # if not dirName in fileMap: + # fileMap[dirName] = [] + # fileMap[dirName].append(localFileName) + + # for dirName in fileMap: + # for localFileName in fileMap[dirName]: + # ???? + + # + # Gradle version + # + # 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); + # } + # } + def bundle(self): - if not os.path.isfile(self.xmlFile) or True: + if not os.path.isfile(self.xmlFile): self.copyQtDeps() self.scanQmlImports() self.processFiles() + # if not os.path.isfile(self.qtAssetCacheList): + # self.generateAssetsFileList() diff --git a/hifi_utils.py b/hifi_utils.py index f53258d4f6..c1a52ed842 100644 --- a/hifi_utils.py +++ b/hifi_utils.py @@ -97,16 +97,12 @@ def downloadFile(url, hash=None, hasher=hashlib.sha512(), retries=3): else: tempFileName, headers = urllib.request.urlretrieve(url) - # for some reason the hash we get back from the downloaded file is sometimes wrong if we check it right away - # but if we examine the file later, it is correct. - time.sleep(3) downloadHash = hashFile(tempFileName, hasher) # Verify the hash if hash is not None and hash != downloadHash: print("Try {}: Downloaded file {} hash {} does not match expected hash {} for url {}".format(i + 1, tempFileName, downloadHash, hash, url)) os.remove(tempFileName) continue - return tempFileName raise RuntimeError("Downloaded file hash {} does not match expected hash {} for\n{}".format(downloadHash, hash, url)) diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index 5492109864..6d241c595f 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -189,6 +189,18 @@ endif() #hifi_utils.downloadAndExtract(url, dest, hash) hifi_utils.downloadAndExtract(url, dest) + print("Installing additional android archives") + androidPackages = hifi_android.getPlatformPackages() + for packageName in androidPackages: + package = androidPackages[packageName] + dest = os.path.join(self.androidPackagePath, packageName) + if os.path.isdir(dest): + continue + url = hifi_android.getPackageUrl(package) + zipFile = package['file'].endswith('.zip') + print("Android archive {}".format(package['file'])) + hifi_utils.downloadAndExtract(url, dest, isZip=zipFile, hash=package['checksum'], hasher=hashlib.md5()) + def writeTag(self): print("Writing tag {} to {}".format(self.tagContents, self.tagFile)) with open(self.tagFile, 'w') as f: @@ -203,6 +215,12 @@ endif() cmakeTemplate = VcpkgRepo.CMAKE_TEMPLATE if not self.args.android: cmakeTemplate += VcpkgRepo.CMAKE_TEMPLATE_NON_ANDROID + else: + precompiled = os.path.realpath(os.path.join(self.path, 'android')) + qtCmakePrefix = os.path.realpath(os.path.join(precompiled, 'qt/lib/cmake')) + cmakeTemplate += 'set(HIFI_ANDROID_PRECOMPILED "{}")\n'.format(precompiled) + cmakeTemplate += 'set(QT_CMAKE_PREFIX_PATH "{}")\n'.format(qtCmakePrefix) + cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath).replace('\\', '/') with open(self.configFilePath, 'w') as f: f.write(cmakeConfig) diff --git a/prebuild.py b/prebuild.py index a758dcbea2..fb54b8d6fe 100644 --- a/prebuild.py +++ b/prebuild.py @@ -43,8 +43,7 @@ def parse_args(): defaultPortsPath = hifi_utils.scriptRelative('cmake', 'ports') from argparse import ArgumentParser parser = ArgumentParser(description='Prepare build dependencies.') - parser.add_argument('--android', action='store_true') - #parser.add_argument('--android', type=str) + parser.add_argument('--android', type=str) parser.add_argument('--debug', action='store_true') parser.add_argument('--force-bootstrap', action='store_true') parser.add_argument('--force-build', action='store_true') @@ -87,6 +86,17 @@ def main(): # here shouldn't invalidte the vcpkg install) pm.cleanBuilds() + # If we're running in android mode, we also need to grab a bunch of additional binaries + # (this logic is all migrated from the old setupDependencies tasks in gradle) + if args.android: + # Find the target location + appPath = hifi_utils.scriptRelative('android/apps/' + args.android) + # Copy the non-Qt libraries specified in the config in hifi_android.py + hifi_android.copyAndroidLibs(pm.androidPackagePath, appPath) + # Determine the Qt package path + qtPath = os.path.join(pm.androidPackagePath, 'qt') + hifi_android.QtPackager(appPath, qtPath).bundle() + # Write the vcpkg config to the build directory last pm.writeConfig()