import java.nio.file.Paths import org.gradle.internal.logging.text.StyledTextOutput; import org.gradle.internal.logging.text.StyledTextOutputFactory; import static org.gradle.internal.logging.text.StyledTextOutput.Style; import org.gradle.internal.os.OperatingSystem; import org.gradle.api.Project; defaultTasks 'cleanPath', 'archiveZip' def protobufInstallDir() { return new File("./third_party/protobuf-3.0.0/install/linux-x86").getCanonicalPath() } def joinPath(String first, String... more) { return Paths.get(first, more).toString() } task prepare_proto_before { def protocBinDir = protobufInstallDir() + "/bin" doLast { // Install python-protobuf exec { workingDir "./third_party/protobuf-3.0.0/python" environment 'PATH', "$protocBinDir:${environment.PATH}" commandLine "python", "setup.py", "install", "--user" } // Generate nano-pb requirements exec { workingDir getExternalPath() + '/nanopb-c/generator/proto' environment 'PATH', "$protocBinDir:${environment.PATH}" commandLine 'make' } } } task prepare_proto(dependsOn: prepare_proto_before) { doLast { exec { commandLine "python" args = ["ab_info.py"] } exec { commandLine "dpkg-query" args = ["--list", "*env*"] } } } buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' } } allprojects { buildDir = getOutPath() repositories { google() jcenter() } } abstract class Toolchain { protected String androidVersion_; protected String ndkVersion_; abstract String getAndroidNDKPath(); abstract String getCMakePath(); abstract String getNinjaPath(); public String getAndroidVersion() { return androidVersion_; } public String getNdkVersion() { return ndkVersion_; } public String getBuildKey(String arch, String stl) { return arch + '_SDK' + androidVersion_ + "_NDK" + ndkVersion_ + '_' + stl } protected String getNdkVersionFromPropertiesFile() { def f = new File(getAndroidNDKPath(), 'source.properties') if (!f.exists()) { println "Warning: can't get NDK version" return "UNKNOWN" } else { Properties props = new Properties() f.withInputStream { props.load(it) } def ver = props."Pkg.Revision" def parts = ver.findAll("[^.]+") if (parts.size()>1) return parts[0] + "." + parts[1] else return parts[0] } } } class SpecificToolchain extends Toolchain { SpecificToolchain(String androidVersion, String ndkVersion) { androidVersion_ = androidVersion ndkVersion_ = ndkVersion } public String getAndroidNDKPath() { return new File("../prebuilts/ndk/" + ndkVersion_).getCanonicalPath() } public String getCMakePath() { return new File("../prebuilts/cmake/linux-x86/bin/cmake").getCanonicalPath() } public String getNinjaPath() { return new File("../prebuilts/cmake/linux-x86/bin/ninja").getCanonicalPath() } } class LocalToolchain extends Toolchain { Project project_; String sdkPath_; String ndkPath_; String cmakePath_; String ninjaPath_; String adbPath_; LocalToolchain(Project project, String androidVersion) { project_ = project androidVersion_ = androidVersion sdkPath_ = System.getenv("ANDROID_HOME") if(sdkPath_ == null) { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) sdkPath_ = properties.getProperty('sdk.dir') } if(sdkPath_ == null) throw new GradleException('Must set ANDROID_HOME or sdk.dir in local.properties') ndkPath_ = System.getenv("ANDROID_NDK") if(ndkPath_ == null) { ndkPath_ = project_.joinPath(sdkPath_, 'ndk-bundle') if (!project_.file(ndkPath_).exists()) throw new GradleException('No NDK found in SDK: must set ANDROID_NDK') } ndkVersion_ = getNdkVersionFromPropertiesFile() cmakePath_ = findCMakeTool("CMAKE", "cmake") ninjaPath_ = findCMakeTool("NINJA", "ninja") adbPath_ = project_.joinPath(sdkPath_, 'platform-tools', 'adb') } String getAndroidNDKPath() { return ndkPath_; } String getCMakePath() { return cmakePath_; } String getNinjaPath() { return ninjaPath_; } String getAdbPath() { return adbPath_; } String findCMakeTool(String envVar, String name) { def tool = System.getenv(envVar); if (tool) { return tool; } def osname = OperatingSystem.current().isWindows() ? name + '.exe' : name def tools = project_.fileTree( dir: project_.joinPath(sdkPath_, 'cmake'), include: ['**/bin/'+osname] ) if (tools==null || tools.size() == 0) { throw new GradleException('No ' + osname + ' found in ' + sdkPath_ + '/cmake') } return tools.getFiles().last().toString(); } } def getBuildPath() { return new File("./build").getCanonicalPath() } def getOutPath() { return new File("../out").getCanonicalPath() } def getPackagePath() { return new File("../package").getCanonicalPath() } def getTempPath() { return new File("../.temp").getCanonicalPath() } def getExternalPath() { def f = new File("../external/"); if ( !f.exists() ) f = new File("../../../external/"); return f.getCanonicalPath() } def useNinja() { return true; } def cmake(projectFolder, workingFolder, outputFolder, arch, toolchain, stl, threadChecks, buildTuningFork) { def ndkPath = toolchain.getAndroidNDKPath() def toolchainFilePath = ndkPath + "/build/cmake/android.toolchain.cmake" def externalPath = getExternalPath(); def androidVersion = toolchain.getAndroidVersion() def ninjaPath = toolchain.getNinjaPath() def buildtfval = buildTuningFork ? "ON" : "OFF" def protocBinDir = protobufInstallDir() + "/bin" mkdir workingFolder mkdir outputFolder def threadFlags = "" if (threadChecks) { threadFlags = "-DGAMESDK_THREAD_CHECKS=1" } else { threadFlags = "-DGAMESDK_THREAD_CHECKS=0" } def cmdLine = [toolchain.getCMakePath(), "$projectFolder", "-DCMAKE_BUILD_TYPE=Release", "-DANDROID_PLATFORM=android-$androidVersion", "-DANDROID_NDK=$ndkPath", "-DANDROID_STL=$stl", "-DANDROID_ABI=$arch", "-DCMAKE_CXX_FLAGS=", "-DCMAKE_TOOLCHAIN_FILE=$toolchainFilePath", "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=$outputFolder", "-DGAMESDK_BUILD_TUNINGFORK=$buildtfval", "-DEXTERNAL_ROOT=$externalPath", threadFlags] if (useNinja()) { cmdLine += ["-DCMAKE_MAKE_PROGRAM=" + "$ninjaPath", "-GNinja"] } exec { environment "PATH", "$protocBinDir:${environment.PATH}" workingDir workingFolder commandLine cmdLine } } def buildNativeModules(arch, Toolchain toolchain, stl, threadChecks, buildtf, subdir = "src") { def buildKey = toolchain.getBuildKey(arch, stl) def workingFolder = joinPath(getTempPath(), buildKey, '.cmake') def outputFolder = joinPath(getOutPath(), buildKey) def cmakeProjectLocation = joinPath("$projectDir", subdir) cmake(cmakeProjectLocation, workingFolder, outputFolder, arch, toolchain, stl, threadChecks, buildtf) def cmdLine = useNinja() ? [toolchain.getNinjaPath()] : ["make", "-j"] exec { workingDir workingFolder commandLine cmdLine } return [arch: arch, buildKey: buildKey] } def buildNativeModules(arch, androidVersion, ndkVersion, stl, threadChecks, buildtf) { return buildNativeModules(arch, new SpecificToolchain(androidVersion, ndkVersion), stl, threadChecks, buildtf) } task cleanPath(type: Delete) { delete getOutPath() delete getPackagePath() delete getTempPath() delete getBuildPath() } // Create outAr from the contents of inArs // All files taken/created in dir def repackArchives(dir, inArs, outAr) { def cmd = /pushd $dir &&/ inArs.each { cmd <<= /ar -x $it &&/ } cmd <<= /ar -qc $outAr *.o && rm *.o && popd/ ['/bin/bash', '-c', cmd.toString()].execute().waitFor() } def sdkCopy(buildInfo, outFolder, all = true, staticToo = false, useFullBuildKey = false, flattenLibDirs = false, shared = true) { def arch = buildInfo.arch def buildKey = buildInfo.buildKey def cmakeFolder = joinPath(getTempPath(), buildKey, '.cmake') def buildFolder = joinPath(getPackagePath(), outFolder) def libBuildFolder = joinPath(buildFolder, 'libs', useFullBuildKey ? buildKey : arch, 'lib') if (shared) { copy { from file(cmakeFolder) include all ? "*/lib*.so" : "swappy*/lib*.so" into file(libBuildFolder) includeEmptyDirs = false if (flattenLibDirs) { eachFile { path = name } } } } if (staticToo) { def staticsFolder = joinPath(getOutPath(), buildKey) def staticLibsBuildFolder = joinPath(buildFolder, 'libs', buildKey) def staticLibs = ['libswappy_static.a', 'libswappyVk_static.a'] if (all) staticLibs += 'libtuningfork_static.a' repackArchives(staticsFolder, staticLibs, 'libgamesdk.a') copy { from file(staticsFolder) include "libgamesdk.a" into file(staticLibsBuildFolder) } } } def copyExtras(outFolder, all = true) { def buildFolder = getPackagePath() + '/' + outFolder def headerFolder = './include' def aarFolder = joinPath(getOutPath(), 'outputs', 'aar') def includeBuildFolder = joinPath(buildFolder, 'include') def aarBuildFolder = joinPath(buildFolder, 'aar') copy { from file(headerFolder) include all ? "*/*.h" : "swappy*/*.h" into file(includeBuildFolder) includeEmptyDirs = false } copy { from file(aarFolder) into file(aarBuildFolder) includeEmptyDirs = false } } // The latest Unity is using NDK 16b and SDK 21 with gnu stl def defaultAbis() { return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"] } def unityNativeBuild(withTuningFork=false) { def threadChecks = false return defaultAbis().collect { buildNativeModules(it, "21", "r16", "gnustl_static", threadChecks, withTuningFork) } } def sdkNativeBuild(withTuningFork = true) { def threadChecks = false return defaultAbis().collectMany { [ buildNativeModules(it, "26", "r16", "gnustl_static", threadChecks, withTuningFork) ] + [ buildNativeModules(it, "28", "r17", "gnustl_static", threadChecks, withTuningFork) ] + [ buildNativeModules(it, "28", "r17", "c++_static", threadChecks, withTuningFork) ] } } def getLocalToolchain() { def kLocalMinSdk = project.hasProperty("GAMESDK_ANDROID_SDK_VERSION") ? project.GAMESDK_ANDROID_SDK_VERSION : "26" return new LocalToolchain(project, kLocalMinSdk) } def localNativeBuild(withTuningFork = false, subdir = "src") { def toolchain = getLocalToolchain(); def stl = "c++_static" def threadChecks = true return defaultAbis().collect { buildNativeModules(it, toolchain , stl, threadChecks, withTuningFork, subdir) } } class BuildTask extends DefaultTask { } // Just build swappy shared libraries task swappyUnityBuild(type: BuildTask) { dependsOn ':extras:assembleRelease' ext.packageName = 'swappyUnity' ext.flattenLibs = true ext.withTuningFork = false ext.withSharedLibs = true ext.withStaticLibs = true ext.withFullBuildKey = false ext.nativeBuild = { tf -> unityNativeBuild(tf) } } // Full build including tuning fork for unity, shared libraries only task unityBuild(type: BuildTask) { dependsOn ':extras:assembleRelease', prepare_proto ext.packageName = 'unity' ext.flattenLibs = true ext.withTuningFork = true ext.withSharedLibs = true ext.withStaticLibs = false ext.withFullBuildKey = false ext.nativeBuild = { tf -> unityNativeBuild(tf) } } // Build everything task sdkBuild(type: BuildTask) { dependsOn ':extras:assembleRelease', prepare_proto ext.packageName = 'gamesdk' ext.flattenLibs = false ext.withTuningFork = true ext.withSharedLibs = true ext.withStaticLibs = true ext.withFullBuildKey = true ext.nativeBuild = { tf -> sdkNativeBuild(tf) } } // Build using local SDK, no tuning fork task localBuild(type: BuildTask) { dependsOn ':extras:assembleRelease' ext.packageName = 'local' ext.flattenLibs = false ext.withTuningFork = false; ext.withSharedLibs = true ext.withStaticLibs = true; ext.withFullBuildKey = false; ext.nativeBuild = { tf -> localNativeBuild(tf) } } // Build using local SDK, with tuning fork task localTfBuild(type: BuildTask) { dependsOn ':extras:assembleRelease', prepare_proto ext.packageName = 'localtf' ext.flattenLibs = false ext.withTuningFork = true; ext.withSharedLibs = true ext.withStaticLibs = true; ext.withFullBuildKey = false; ext.nativeBuild = { tf -> localNativeBuild(tf) } } tasks.withType(BuildTask) { doLast { nativeBuild(withTuningFork).each { sdkCopy(it, packageName, withTuningFork, withStaticLibs, withFullBuildKey, flattenLibs, withSharedLibs) } copyExtras(packageName, withTuningFork) } } task localUnitTests { // These unit tests require a connected ARM64 device to run doLast { def buildInfo = localNativeBuild(true, "test") def buildKey = buildInfo.buildKey.findAll{it.contains('arm64-v8a')}[0] def cmakeFolder = getTempPath() + '/' + buildKey + '/.cmake/tuningfork' def toolchain = getLocalToolchain(); def adb = toolchain.getAdbPath(); exec { workingDir cmakeFolder commandLine adb, "push", "tuningfork_test", "/data/local/tmp" } exec { workingDir cmakeFolder commandLine adb, "shell", "/data/local/tmp/tuningfork_test" } } } // Zipping things up def addZipTask(name, buildTask, zipName, rootName = "gamesdk") { def packPath = buildTask.packageName tasks.register(name, Zip) { dependsOn buildTask def buildFolder = joinPath(getPackagePath(), packPath) baseName = joinPath(buildFolder, zipName) from fileTree(buildFolder) exclude "*.zip" into rootName doLast { def outFolder = getBuildPath(); mkdir outFolder; copy { from file(baseName + '.zip') into outFolder } def out = services.get(StyledTextOutputFactory).create("output") out.style(Style.Identifier).text('\n' + rootName +'.zip is in ') .style(Style.ProgressStatus) .println(baseName + '.zip' ); } } } addZipTask("swappyUnityZip", swappyUnityBuild, "builds") addZipTask("unityZip", unityBuild, "builds") addZipTask("archiveZip", localBuild, "gamesdk") addZipTask("archiveTfZip", localTfBuild, "gamesdk") addZipTask("gamesdkZip", sdkBuild, "gamesdk")