text: # -*- coding: utf-8 -*-
  
  #-------------------------------------------------------------------------
  # drawElements Quality Program utilities
  # --------------------------------------
  #
  # Copyright 2017 The Android Open Source Project
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #      http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  #-------------------------------------------------------------------------
  
  # \todo [2017-04-10 pyry]
  # * Use smarter asset copy in main build
  #   * cmake -E copy_directory doesn't copy timestamps which will cause
  #     assets to be always re-packaged
  # * Consider adding an option for downloading SDK & NDK
  
  import os
  import re
  import sys
  import glob
  import string
  import shutil
  import argparse
  import tempfile
  import xml.etree.ElementTree
  
  # Import from <root>/scripts
  sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
  
  from build.common import *
  from build.config import *
  from build.build import *
  
  class SDKEnv:
  	def __init__(self, path):
  		self.path				= path
  		self.buildToolsVersion	= SDKEnv.selectBuildToolsVersion(self.path)
  
  	@staticmethod
  	def getBuildToolsVersions (path):
  		buildToolsPath	= os.path.join(path, "build-tools")
  		versions		= []
  
  		if os.path.exists(buildToolsPath):
  			for item in os.listdir(buildToolsPath):
  				m = re.match(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$', item)
  				if m != None:
  					versions.append((int(m.group(1)), int(m.group(2)), int(m.group(3))))
  
  		return versions
  
  	@staticmethod
  	def selectBuildToolsVersion (path):
  		preferred	= [(25, 0, 2)]
  		versions	= SDKEnv.getBuildToolsVersions(path)
  
  		if len(versions) == 0:
  			return (0,0,0)
  
  		for candidate in preferred:
  			if candidate in versions:
  				return candidate
  
  		# Pick newest
  		versions.sort()
  		return versions[-1]
  
  	def getPlatformLibrary (self, apiVersion):
  		return os.path.join(self.path, "platforms", "android-%d" % apiVersion, "android.jar")
  
  	def getBuildToolsPath (self):
  		return os.path.join(self.path, "build-tools", "%d.%d.%d" % self.buildToolsVersion)
  
  class NDKEnv:
  	def __init__(self, path):
  		self.path		= path
  		self.version	= NDKEnv.detectVersion(self.path)
  		self.hostOsName	= NDKEnv.detectHostOsName(self.path)
  
  	@staticmethod
  	def getKnownAbis ():
  		return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
  
  	@staticmethod
  	def getAbiPrebuiltsName (abiName):
  		prebuilts = {
  			"armeabi-v7a":	'android-arm',
  			"arm64-v8a":	'android-arm64',
  			"x86":			'android-x86',
  			"x86_64":		'android-x86_64',
  		}
  
  		if not abiName in prebuilts:
  			raise Exception("Unknown ABI: " + abiName)
  
  		return prebuilts[abiName]
  
  	@staticmethod
  	def detectVersion (path):
  		propFilePath = os.path.join(path, "source.properties")
  		try:
  			with open(propFilePath) as propFile:
  				for line in propFile:
  					keyValue = map(lambda x: string.strip(x), line.split("="))
  					if keyValue[0] == "Pkg.Revision":
  						versionParts = keyValue[1].split(".")
  						return tuple(map(int, versionParts[0:2]))
  		except Exception as e:
  			raise Exception("Failed to read source prop file '%s': %s" % (propFilePath, str(e)))
  		except:
  			raise Exception("Failed to read source prop file '%s': unkown error")
  
  		raise Exception("Failed to detect NDK version (does %s/source.properties have Pkg.Revision?)" % path)
  
  	@staticmethod
  	def isHostOsSupported (hostOsName):
  		os			= HostInfo.getOs()
  		bits		= HostInfo.getArchBits()
  		hostOsParts	= hostOsName.split('-')
  
  		if len(hostOsParts) > 1:
  			assert(len(hostOsParts) == 2)
  			assert(hostOsParts[1] == "x86_64")
  
  			if bits != 64:
  				return False
  
  		if os == HostInfo.OS_WINDOWS:
  			return hostOsParts[0] == 'windows'
  		elif os == HostInfo.OS_LINUX:
  			return hostOsParts[0] == 'linux'
  		elif os == HostInfo.OS_OSX:
  			return hostOsParts[0] == 'darwin'
  		else:
  			raise Exception("Unhandled HostInfo.getOs() '%d'" % os)
  
  	@staticmethod
  	def detectHostOsName (path):
  		hostOsNames = [
  			"windows",
  			"windows-x86_64",
  			"darwin-x86",
  			"darwin-x86_64",
  			"linux-x86",
  			"linux-x86_64"
  		]
  
  		for name in hostOsNames:
  			if os.path.exists(os.path.join(path, "prebuilt", name)):
  				return name
  
  		raise Exception("Failed to determine NDK host OS")
  
  class Environment:
  	def __init__(self, sdk, ndk):
  		self.sdk		= sdk
  		self.ndk		= ndk
  
  class Configuration:
  	def __init__(self, env, buildPath, abis, nativeApi, nativeBuildType, gtfTarget, verbose, layers, angle):
  		self.env				= env
  		self.sourcePath			= DEQP_DIR
  		self.buildPath			= buildPath
  		self.abis				= abis
  		self.nativeApi			= nativeApi
  		self.javaApi			= 22
  		self.nativeBuildType	= nativeBuildType
  		self.gtfTarget			= gtfTarget
  		self.verbose			= verbose
  		self.layers				= layers
  		self.angle				= angle
  		self.cmakeGenerator		= selectFirstAvailableGenerator([NINJA_GENERATOR, MAKEFILE_GENERATOR, NMAKE_GENERATOR])
  
  	def check (self):
  		if self.cmakeGenerator == None:
  			raise Exception("Failed to find build tools for CMake")
  
  		if not os.path.exists(self.env.ndk.path):
  			raise Exception("Android NDK not found at %s" % self.env.ndk.path)
  
  		if not NDKEnv.isHostOsSupported(self.env.ndk.hostOsName):
  			raise Exception("NDK '%s' is not supported on this machine" % self.env.ndk.hostOsName)
  
  		if self.env.ndk.version[0] < 15:
  			raise Exception("Android NDK version %d is not supported; build requires NDK version >= 15" % (self.env.ndk.version[0]))
  
  		if self.env.sdk.buildToolsVersion == (0,0,0):
  			raise Exception("No build tools directory found at %s" % os.path.join(self.env.sdk.path, "build-tools"))
  
  		androidBuildTools = ["aapt", "zipalign", "dx"]
  		for tool in androidBuildTools:
  			if which(tool, [self.env.sdk.getBuildToolsPath()]) == None:
  				raise Exception("Missing Android build tool: %s" % toolPath)
  
  		requiredToolsInPath = ["javac", "jar", "jarsigner", "keytool"]
  		for tool in requiredToolsInPath:
  			if which(tool) == None:
  				raise Exception("%s not in PATH" % tool)
  
  def log (config, msg):
  	if config.verbose:
  		print msg
  
  def executeAndLog (config, args):
  	if config.verbose:
  		print " ".join(args)
  	execute(args)
  
  # Path components
  
  class ResolvablePathComponent:
  	def __init__ (self):
  		pass
  
  class SourceRoot (ResolvablePathComponent):
  	def resolve (self, config):
  		return config.sourcePath
  
  class BuildRoot (ResolvablePathComponent):
  	def resolve (self, config):
  		return config.buildPath
  
  class NativeBuildPath (ResolvablePathComponent):
  	def __init__ (self, abiName):
  		self.abiName = abiName
  
  	def resolve (self, config):
  		return getNativeBuildPath(config, self.abiName)
  
  class GeneratedResSourcePath (ResolvablePathComponent):
  	def __init__ (self, package):
  		self.package = package
  
  	def resolve (self, config):
  		packageComps	= self.package.getPackageName(config).split('.')
  		packageDir		= os.path.join(*packageComps)
  
  		return os.path.join(config.buildPath, self.package.getAppDirName(), "src", packageDir, "R.java")
  
  def resolvePath (config, path):
  	resolvedComps = []
  
  	for component in path:
  		if isinstance(component, ResolvablePathComponent):
  			resolvedComps.append(component.resolve(config))
  		else:
  			resolvedComps.append(str(component))
  
  	return os.path.join(*resolvedComps)
  
  def resolvePaths (config, paths):
  	return list(map(lambda p: resolvePath(config, p), paths))
  
  class BuildStep:
  	def __init__ (self):
  		pass
  
  	def getInputs (self):
  		return []
  
  	def getOutputs (self):
  		return []
  
  	@staticmethod
  	def expandPathsToFiles (paths):
  		"""
  		Expand mixed list of file and directory paths into a flattened list
  		of files. Any non-existent input paths are preserved as is.
  		"""
  
  		def getFiles (dirPath):
  			for root, dirs, files in os.walk(dirPath):
  				for file in files:
  					yield os.path.join(root, file)
  
  		files = []
  		for path in paths:
  			if os.path.isdir(path):
  				files += list(getFiles(path))
  			else:
  				files.append(path)
  
  		return files
  
  	def isUpToDate (self, config):
  		inputs				= resolvePaths(config, self.getInputs())
  		outputs				= resolvePaths(config, self.getOutputs())
  
  		assert len(inputs) > 0 and len(outputs) > 0
  
  		expandedInputs		= BuildStep.expandPathsToFiles(inputs)
  		expandedOutputs		= BuildStep.expandPathsToFiles(outputs)
  
  		existingInputs		= filter(os.path.exists, expandedInputs)
  		existingOutputs		= filter(os.path.exists, expandedOutputs)
  
  		if len(existingInputs) != len(expandedInputs):
  			for file in expandedInputs:
  				if file not in existingInputs:
  					print "ERROR: Missing input file: %s" % file
  			die("Missing input files")
  
  		if len(existingOutputs) != len(expandedOutputs):
  			return False # One or more output files are missing
  
  		lastInputChange		= max(map(os.path.getmtime, existingInputs))
  		firstOutputChange	= min(map(os.path.getmtime, existingOutputs))
  
  		return lastInputChange <= firstOutputChange
  
  	def update (config):
  		die("BuildStep.update() not implemented")
  
  def getNativeBuildPath (config, abiName):
  	return os.path.join(config.buildPath, "%s-%s-%d" % (abiName, config.nativeBuildType, config.nativeApi))
  
  def buildNativeLibrary (config, abiName):
  	def makeNDKVersionString (version):
  		minorVersionString = (chr(ord('a') + version[1]) if version[1] > 0 else "")
  		return "r%d%s" % (version[0], minorVersionString)
  
  	def getBuildArgs (config, abiName):
  		args = ['-DDEQP_TARGET=android',
  				'-DDEQP_TARGET_TOOLCHAIN=ndk-modern',
  				'-DCMAKE_C_FLAGS=-Werror',
  				'-DCMAKE_CXX_FLAGS=-Werror',
  				'-DANDROID_NDK_HOST_OS=%s' % config.env.ndk.hostOsName,
  				'-DANDROID_NDK_PATH=%s' % config.env.ndk.path,
  				'-DANDROID_ABI=%s' % abiName,
  				'-DDE_ANDROID_API=%s' % config.nativeApi,
  				'-DGLCTS_GTF_TARGET=%s' % config.gtfTarget]
  
  		if config.angle is not None:
  			args.append('-DANGLE_LIBS=%s' % os.path.join(config.angle, abiName))
  
  		return args
  
  	nativeBuildPath	= getNativeBuildPath(config, abiName)
  	buildConfig		= BuildConfig(nativeBuildPath, config.nativeBuildType, getBuildArgs(config, abiName))
  
  	build(buildConfig, config.cmakeGenerator, ["deqp"])
  
  def executeSteps (config, steps):
  	for step in steps:
  		if not step.isUpToDate(config):
  			step.update(config)
  
  def parsePackageName (manifestPath):
  	tree = xml.etree.ElementTree.parse(manifestPath)
  
  	if not 'package' in tree.getroot().attrib:
  		raise Exception("'package' attribute missing from root element in %s" % manifestPath)
  
  	return tree.getroot().attrib['package']
  
  class PackageDescription:
  	def __init__ (self, appDirName, appName, hasResources = True):
  		self.appDirName		= appDirName
  		self.appName		= appName
  		self.hasResources	= hasResources
  
  	def getAppName (self):
  		return self.appName
  
  	def getAppDirName (self):
  		return self.appDirName
  
  	def getPackageName (self, config):
  		manifestPath	= resolvePath(config, self.getManifestPath())
  
  		return parsePackageName(manifestPath)
  
  	def getManifestPath (self):
  		return [SourceRoot(), "android", self.appDirName, "AndroidManifest.xml"]
  
  	def getResPath (self):
  		return [SourceRoot(), "android", self.appDirName, "res"]
  
  	def getSourcePaths (self):
  		return [
  				[SourceRoot(), "android", self.appDirName, "src"]
  			]
  
  	def getAssetsPath (self):
  		return [BuildRoot(), self.appDirName, "assets"]
  
  	def getClassesJarPath (self):
  		return [BuildRoot(), self.appDirName, "bin", "classes.jar"]
  
  	def getClassesDexPath (self):
  		return [BuildRoot(), self.appDirName, "bin", "classes.dex"]
  
  	def getAPKPath (self):
  		return [BuildRoot(), self.appDirName, "bin", self.appName + ".apk"]
  
  # Build step implementations
  
  class BuildNativeLibrary (BuildStep):
  	def __init__ (self, abi):
  		self.abi = abi
  
  	def isUpToDate (self, config):
  		return False
  
  	def update (self, config):
  		log(config, "BuildNativeLibrary: %s" % self.abi)
  		buildNativeLibrary(config, self.abi)
  
  class GenResourcesSrc (BuildStep):
  	def __init__ (self, package):
  		self.package = package
  
  	def getInputs (self):
  		return [self.package.getResPath(), self.package.getManifestPath()]
  
  	def getOutputs (self):
  		return [[GeneratedResSourcePath(self.package)]]
  
  	def update (self, config):
  		aaptPath	= which("aapt", [config.env.sdk.getBuildToolsPath()])
  		dstDir		= os.path.dirname(resolvePath(config, [GeneratedResSourcePath(self.package)]))
  
  		if not os.path.exists(dstDir):
  			os.makedirs(dstDir)
  
  		executeAndLog(config, [
  				aaptPath,
  				"package",
  				"-f",
  				"-m",
  				"-S", resolvePath(config, self.package.getResPath()),
  				"-M", resolvePath(config, self.package.getManifestPath()),
  				"-J", resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "src"]),
  				"-I", config.env.sdk.getPlatformLibrary(config.javaApi)
  			])
  
  # Builds classes.jar from *.java files
  class BuildJavaSource (BuildStep):
  	def __init__ (self, package, libraries = []):
  		self.package	= package
  		self.libraries	= libraries
  
  	def getSourcePaths (self):
  		srcPaths = self.package.getSourcePaths()
  
  		if self.package.hasResources:
  			srcPaths.append([BuildRoot(), self.package.getAppDirName(), "src"]) # Generated sources
  
  		return srcPaths
  
  	def getInputs (self):
  		inputs = self.getSourcePaths()
  
  		for lib in self.libraries:
  			inputs.append(lib.getClassesJarPath())
  
  		return inputs
  
  	def getOutputs (self):
  		return [self.package.getClassesJarPath()]
  
  	def update (self, config):
  		srcPaths	= resolvePaths(config, self.getSourcePaths())
  		srcFiles	= BuildStep.expandPathsToFiles(srcPaths)
  		jarPath		= resolvePath(config, self.package.getClassesJarPath())
  		objPath		= resolvePath(config, [BuildRoot(), self.package.getAppDirName(), "obj"])
  		classPaths	= [objPath] + [resolvePath(config, lib.getClassesJarPath()) for lib in self.libraries]
  		pathSep		= ";" if HostInfo.getOs() == HostInfo.OS_WINDOWS else ":"
  
  		if os.path.exists(objPath):
  			shutil.rmtree(objPath)
  
  		os.makedirs(objPath)
  
  		for srcFile in srcFiles:
  			executeAndLog(config, [
  					"javac",
  					"-source", "1.7",
  					"-target", "1.7",
  					"-d", objPath,
  					"-bootclasspath", config.env.sdk.getPlatformLibrary(config.javaApi),
  					"-classpath", pathSep.join(classPaths),
  					"-sourcepath", pathSep.join(srcPaths),
  					srcFile
  				])
  
  		if not os.path.exists(os.path.dirname(jarPath)):
  			os.makedirs(os.path.dirname(jarPath))
  
  		try:
  			pushWorkingDir(objPath)
  			executeAndLog(config, [
  					"jar",
  					"cf",
  					jarPath,
  					"."
  				])
  		finally:
  			popWorkingDir()
  
  class BuildDex (BuildStep):
  	def __init__ (self, package, libraries):
  		self.package	= package
  		self.libraries	= libraries
  
  	def getInputs (self):
  		return [self.package.getClassesJarPath()] + [lib.getClassesJarPath() for lib in self.libraries]
  
  	def getOutputs (self):
  		return [self.package.getClassesDexPath()]
  
  	def update (self, config):
  		dxPath		= which("dx", [config.env.sdk.getBuildToolsPath()])
  		srcPaths	= resolvePaths(config, self.getInputs())
  		dexPath		= resolvePath(config, self.package.getClassesDexPath())
  		jarPaths	= [resolvePath(config, self.package.getClassesJarPath())]
  
  		for lib in self.libraries:
  			jarPaths.append(resolvePath(config, lib.getClassesJarPath()))
  
  		executeAndLog(config, [
  				dxPath,
  				"--dex",
  				"--output", dexPath
  			] + jarPaths)
  
  class CreateKeystore (BuildStep):
  	def __init__ (self):
  		self.keystorePath	= [BuildRoot(), "debug.keystore"]
  
  	def getOutputs (self):
  		return [self.keystorePath]
  
  	def isUpToDate (self, config):
  		return os.path.exists(resolvePath(config, self.keystorePath))
  
  	def update (self, config):
  		executeAndLog(config, [
  				"keytool",
  				"-genkey",
  				"-keystore", resolvePath(config, self.keystorePath),
  				"-storepass", "android",
  				"-alias", "androiddebugkey",
  				"-keypass", "android",
  				"-keyalg", "RSA",
  				"-keysize", "2048",
  				"-validity", "10000",
  				"-dname", "CN=, OU=, O=, L=, S=, C=",
  			])
  
  # Builds APK without code
  class BuildBaseAPK (BuildStep):
  	def __init__ (self, package, libraries = []):
  		self.package	= package
  		self.libraries	= libraries
  		self.dstPath	= [BuildRoot(), self.package.getAppDirName(), "tmp", "base.apk"]
  
  	def getResPaths (self):
  		paths = []
  		for pkg in [self.package] + self.libraries:
  			if pkg.hasResources:
  				paths.append(pkg.getResPath())
  		return paths
  
  	def getInputs (self):
  		return [self.package.getManifestPath()] + self.getResPaths()
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	def update (self, config):
  		aaptPath	= which("aapt", [config.env.sdk.getBuildToolsPath()])
  		dstPath		= resolvePath(config, self.dstPath)
  
  		if not os.path.exists(os.path.dirname(dstPath)):
  			os.makedirs(os.path.dirname(dstPath))
  
  		args = [
  			aaptPath,
  			"package",
  			"-f",
  			"-M", resolvePath(config, self.package.getManifestPath()),
  			"-I", config.env.sdk.getPlatformLibrary(config.javaApi),
  			"-F", dstPath,
  		]
  
  		for resPath in self.getResPaths():
  			args += ["-S", resolvePath(config, resPath)]
  
  		if config.verbose:
  			args.append("-v")
  
  		executeAndLog(config, args)
  
  def addFilesToAPK (config, apkPath, baseDir, relFilePaths):
  	aaptPath		= which("aapt", [config.env.sdk.getBuildToolsPath()])
  	maxBatchSize	= 25
  
  	pushWorkingDir(baseDir)
  	try:
  		workQueue = list(relFilePaths)
  
  		while len(workQueue) > 0:
  			batchSize	= min(len(workQueue), maxBatchSize)
  			items		= workQueue[0:batchSize]
  
  			executeAndLog(config, [
  					aaptPath,
  					"add",
  					"-f", apkPath,
  				] + items)
  
  			del workQueue[0:batchSize]
  	finally:
  		popWorkingDir()
  
  def addFileToAPK (config, apkPath, baseDir, relFilePath):
  	addFilesToAPK(config, apkPath, baseDir, [relFilePath])
  
  class AddJavaToAPK (BuildStep):
  	def __init__ (self, package):
  		self.package	= package
  		self.srcPath	= BuildBaseAPK(self.package).getOutputs()[0]
  		self.dstPath	= [BuildRoot(), self.package.getAppDirName(), "tmp", "with-java.apk"]
  
  	def getInputs (self):
  		return [
  				self.srcPath,
  				self.package.getClassesDexPath(),
  			]
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	def update (self, config):
  		srcPath		= resolvePath(config, self.srcPath)
  		dstPath		= resolvePath(config, self.getOutputs()[0])
  		dexPath		= resolvePath(config, self.package.getClassesDexPath())
  
  		shutil.copyfile(srcPath, dstPath)
  		addFileToAPK(config, dstPath, os.path.dirname(dexPath), os.path.basename(dexPath))
  
  class AddAssetsToAPK (BuildStep):
  	def __init__ (self, package, abi):
  		self.package	= package
  		self.buildPath	= [NativeBuildPath(abi)]
  		self.srcPath	= AddJavaToAPK(self.package).getOutputs()[0]
  		self.dstPath	= [BuildRoot(), self.package.getAppDirName(), "tmp", "with-assets.apk"]
  
  	def getInputs (self):
  		return [
  				self.srcPath,
  				self.buildPath + ["assets"]
  			]
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	@staticmethod
  	def getAssetFiles (buildPath):
  		allFiles = BuildStep.expandPathsToFiles([os.path.join(buildPath, "assets")])
  		return [os.path.relpath(p, buildPath) for p in allFiles]
  
  	def update (self, config):
  		srcPath		= resolvePath(config, self.srcPath)
  		dstPath		= resolvePath(config, self.getOutputs()[0])
  		buildPath	= resolvePath(config, self.buildPath)
  		assetFiles	= AddAssetsToAPK.getAssetFiles(buildPath)
  
  		shutil.copyfile(srcPath, dstPath)
  
  		addFilesToAPK(config, dstPath, buildPath, assetFiles)
  
  class AddNativeLibsToAPK (BuildStep):
  	def __init__ (self, package, abis):
  		self.package	= package
  		self.abis		= abis
  		self.srcPath	= AddAssetsToAPK(self.package, "").getOutputs()[0]
  		self.dstPath	= [BuildRoot(), self.package.getAppDirName(), "tmp", "with-native-libs.apk"]
  
  	def getInputs (self):
  		paths = [self.srcPath]
  		for abi in self.abis:
  			paths.append([NativeBuildPath(abi), "libdeqp.so"])
  		return paths
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	def update (self, config):
  		srcPath		= resolvePath(config, self.srcPath)
  		dstPath		= resolvePath(config, self.getOutputs()[0])
  		pkgPath		= resolvePath(config, [BuildRoot(), self.package.getAppDirName()])
  		libFiles	= []
  
  		# Create right directory structure first
  		for abi in self.abis:
  			libSrcPath	= resolvePath(config, [NativeBuildPath(abi), "libdeqp.so"])
  			libRelPath	= os.path.join("lib", abi, "libdeqp.so")
  			libAbsPath	= os.path.join(pkgPath, libRelPath)
  
  			if not os.path.exists(os.path.dirname(libAbsPath)):
  				os.makedirs(os.path.dirname(libAbsPath))
  
  			shutil.copyfile(libSrcPath, libAbsPath)
  			libFiles.append(libRelPath)
  
  			if config.layers:
  				layersGlob = os.path.join(config.layers, abi, "libVkLayer_*.so")
  				libVkLayers = glob.glob(layersGlob)
  				for layer in libVkLayers:
  					layerFilename = os.path.basename(layer)
  					layerRelPath = os.path.join("lib", abi, layerFilename)
  					layerAbsPath = os.path.join(pkgPath, layerRelPath)
  					shutil.copyfile(layer, layerAbsPath)
  					libFiles.append(layerRelPath)
  					print "Adding layer binary: %s" % (layer,)
  
  			if config.angle:
  				angleGlob = os.path.join(config.angle, abi, "lib*_angle.so")
  				libAngle = glob.glob(angleGlob)
  				for lib in libAngle:
  					libFilename = os.path.basename(lib)
  					libRelPath = os.path.join("lib", abi, libFilename)
  					libAbsPath = os.path.join(pkgPath, libRelPath)
  					shutil.copyfile(lib, libAbsPath)
  					libFiles.append(libRelPath)
  					print "Adding ANGLE binary: %s" % (lib,)
  
  		shutil.copyfile(srcPath, dstPath)
  		addFilesToAPK(config, dstPath, pkgPath, libFiles)
  
  class SignAPK (BuildStep):
  	def __init__ (self, package):
  		self.package		= package
  		self.srcPath		= AddNativeLibsToAPK(self.package, []).getOutputs()[0]
  		self.dstPath		= [BuildRoot(), self.package.getAppDirName(), "tmp", "signed.apk"]
  		self.keystorePath	= CreateKeystore().getOutputs()[0]
  
  	def getInputs (self):
  		return [self.srcPath, self.keystorePath]
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	def update (self, config):
  		srcPath		= resolvePath(config, self.srcPath)
  		dstPath		= resolvePath(config, self.dstPath)
  
  		executeAndLog(config, [
  				"jarsigner",
  				"-keystore", resolvePath(config, self.keystorePath),
  				"-storepass", "android",
  				"-keypass", "android",
  				"-signedjar", dstPath,
  				srcPath,
  				"androiddebugkey"
  			])
  
  def getBuildRootRelativeAPKPath (package):
  	return os.path.join(package.getAppDirName(), package.getAppName() + ".apk")
  
  class FinalizeAPK (BuildStep):
  	def __init__ (self, package):
  		self.package		= package
  		self.srcPath		= SignAPK(self.package).getOutputs()[0]
  		self.dstPath		= [BuildRoot(), getBuildRootRelativeAPKPath(self.package)]
  		self.keystorePath	= CreateKeystore().getOutputs()[0]
  
  	def getInputs (self):
  		return [self.srcPath]
  
  	def getOutputs (self):
  		return [self.dstPath]
  
  	def update (self, config):
  		srcPath			= resolvePath(config, self.srcPath)
  		dstPath			= resolvePath(config, self.dstPath)
  		zipalignPath	= os.path.join(config.env.sdk.getBuildToolsPath(), "zipalign")
  
  		executeAndLog(config, [
  				zipalignPath,
  				"-f", "4",
  				srcPath,
  				dstPath
  			])
  
  def getBuildStepsForPackage (abis, package, libraries = []):
  	steps = []
  
  	assert len(abis) > 0
  
  	# Build native code first
  	for abi in abis:
  		steps += [BuildNativeLibrary(abi)]
  
  	# Build library packages
  	for library in libraries:
  		if library.hasResources:
  			steps.append(GenResourcesSrc(library))
  		steps.append(BuildJavaSource(library))
  
  	# Build main package .java sources
  	if package.hasResources:
  		steps.append(GenResourcesSrc(package))
  	steps.append(BuildJavaSource(package, libraries))
  	steps.append(BuildDex(package, libraries))
  
  	# Build base APK
  	steps.append(BuildBaseAPK(package, libraries))
  	steps.append(AddJavaToAPK(package))
  
  	# Add assets from first ABI
  	steps.append(AddAssetsToAPK(package, abis[0]))
  
  	# Add native libs to APK
  	steps.append(AddNativeLibsToAPK(package, abis))
  
  	# Finalize APK
  	steps.append(CreateKeystore())
  	steps.append(SignAPK(package))
  	steps.append(FinalizeAPK(package))
  
  	return steps
  
  def getPackageAndLibrariesForTarget (target):
  	deqpPackage	= PackageDescription("package", "dEQP")
  	ctsPackage	= PackageDescription("openglcts", "Khronos-CTS", hasResources = False)
  
  	if target == 'deqp':
  		return (deqpPackage, [])
  	elif target == 'openglcts':
  		return (ctsPackage, [deqpPackage])
  	else:
  		raise Exception("Uknown target '%s'" % target)
  
  def findNDK ():
  	ndkBuildPath = which('ndk-build')
  	if ndkBuildPath != None:
  		return os.path.dirname(ndkBuildPath)
  	else:
  		return None
  
  def findSDK ():
  	sdkBuildPath = which('android')
  	if sdkBuildPath != None:
  		return os.path.dirname(os.path.dirname(sdkBuildPath))
  	else:
  		return None
  
  def getDefaultBuildRoot ():
  	return os.path.join(tempfile.gettempdir(), "deqp-android-build")
  
  def parseArgs ():
  	nativeBuildTypes	= ['Release', 'Debug', 'MinSizeRel', 'RelWithAsserts', 'RelWithDebInfo']
  	defaultNDKPath		= findNDK()
  	defaultSDKPath		= findSDK()
  	defaultBuildRoot	= getDefaultBuildRoot()
  
  	parser = argparse.ArgumentParser(os.path.basename(__file__),
  		formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  	parser.add_argument('--native-build-type',
  		dest='nativeBuildType',
  		default="RelWithAsserts",
  		choices=nativeBuildTypes,
  		help="Native code build type")
  	parser.add_argument('--build-root',
  		dest='buildRoot',
  		default=defaultBuildRoot,
  		help="Root build directory")
  	parser.add_argument('--abis',
  		dest='abis',
  		default=",".join(NDKEnv.getKnownAbis()),
  		help="ABIs to build")
  	parser.add_argument('--native-api',
  		type=int,
  		dest='nativeApi',
  		default=21,
  		help="Android API level to target in native code")
  	parser.add_argument('--sdk',
  		dest='sdkPath',
  		default=defaultSDKPath,
  		help="Android SDK path",
  		required=(True if defaultSDKPath == None else False))
  	parser.add_argument('--ndk',
  		dest='ndkPath',
  		default=defaultNDKPath,
  		help="Android NDK path",
  		required=(True if defaultNDKPath == None else False))
  	parser.add_argument('-v', '--verbose',
  		dest='verbose',
  		help="Verbose output",
  		default=False,
  		action='store_true')
  	parser.add_argument('--target',
  		dest='target',
  		help='Build target',
  		choices=['deqp', 'openglcts'],
  		default='deqp')
  	parser.add_argument('--kc-cts-target',
  		dest='gtfTarget',
  		default='gles32',
  		choices=['gles32', 'gles31', 'gles3', 'gles2', 'gl'],
  		help="KC-CTS (GTF) target API (only used in openglcts target)")
  	parser.add_argument('--layers-path',
  		dest='layers',
  		default=None,
  		required=False)
  	parser.add_argument('--angle-path',
  		dest='angle',
  		default=None,
  		required=False)
  
  	args = parser.parse_args()
  
  	def parseAbis (abisStr):
  		knownAbis	= set(NDKEnv.getKnownAbis())
  		abis		= []
  
  		for abi in abisStr.split(','):
  			abi = abi.strip()
  			if not abi in knownAbis:
  				raise Exception("Unknown ABI: %s" % abi)
  			abis.append(abi)
  
  		return abis
  
  	# Custom parsing & checks
  	try:
  		args.abis = parseAbis(args.abis)
  		if len(args.abis) == 0:
  			raise Exception("--abis can't be empty")
  	except Exception as e:
  		print "ERROR: %s" % str(e)
  		parser.print_help()
  		sys.exit(-1)
  
  	return args
  
  if __name__ == "__main__":
  	args		= parseArgs()
  
  	ndk			= NDKEnv(os.path.realpath(args.ndkPath))
  	sdk			= SDKEnv(os.path.realpath(args.sdkPath))
  	buildPath	= os.path.realpath(args.buildRoot)
  	env			= Environment(sdk, ndk)
  	config		= Configuration(env, buildPath, abis=args.abis, nativeApi=args.nativeApi, nativeBuildType=args.nativeBuildType, gtfTarget=args.gtfTarget, verbose=args.verbose,
  						 layers=args.layers, angle=args.angle)
  
  	try:
  		config.check()
  	except Exception as e:
  		print "ERROR: %s" % str(e)
  		print ""
  		print "Please check your configuration:"
  		print "  --sdk=%s" % args.sdkPath
  		print "  --ndk=%s" % args.ndkPath
  		sys.exit(-1)
  
  	pkg, libs	= getPackageAndLibrariesForTarget(args.target)
  	steps		= getBuildStepsForPackage(config.abis, pkg, libs)
  
  	executeSteps(config, steps)
  
  	print ""
  	print "Built %s" % os.path.join(buildPath, getBuildRootRelativeAPKPath(pkg))
  