1# -*- coding: utf-8 -*-
2
3#-------------------------------------------------------------------------
4# drawElements Quality Program utilities
5# --------------------------------------
6#
7# Copyright 2015 The Android Open Source Project
8#
9# Licensed under the Apache License, Version 2.0 (the "License");
10# you may not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13#      http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS,
17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20#
21#-------------------------------------------------------------------------
22
23import os
24import re
25import sys
26import shutil
27import string
28import argparse
29import time
30
31import common
32
33def getStoreKeyPasswords (filename):
34	f			= open(filename)
35	storepass	= None
36	keypass		= None
37	for line in f:
38		m = re.search('([a-z]+)\s*\=\s*"([^"]+)"', line)
39		if m != None:
40			if m.group(1) == "storepass":
41				storepass = m.group(2)
42			elif m.group(1) == "keypass":
43				keypass = m.group(2)
44	f.close()
45	if storepass == None or keypass == None:
46		common.die("Could not read signing key passwords")
47	return (storepass, keypass)
48
49def getNativeBuildDir (buildRoot, nativeLib, buildType):
50	buildName = "%s-%d-%s" % (buildType.lower(), nativeLib.apiVersion, nativeLib.abiVersion)
51	return os.path.normpath(os.path.join(buildRoot, "native", buildName))
52
53def getAssetsDir (buildRoot, nativeLib, buildType):
54	return os.path.join(getNativeBuildDir(buildRoot, nativeLib, buildType), "assets")
55
56def buildNative (buildRoot, libTargetDir, nativeLib, buildType):
57	deqpDir		= os.path.normpath(os.path.join(common.ANDROID_DIR, ".."))
58	buildDir	= getNativeBuildDir(buildRoot, nativeLib, buildType)
59	libsDir		= os.path.join(libTargetDir, nativeLib.abiVersion)
60	srcLibFile	= os.path.join(buildDir, common.NATIVE_LIB_NAME)
61	dstLibFile	= os.path.join(libsDir, common.NATIVE_LIB_NAME)
62
63	# Make build directory if necessary
64	if not os.path.exists(buildDir):
65		os.makedirs(buildDir)
66		toolchainFile = '%s/framework/delibs/cmake/toolchain-android-%s.cmake' % (deqpDir, common.ANDROID_NDK_TOOLCHAIN_VERSION)
67		common.execArgsInDirectory([
68				'cmake',
69				'-G%s' % common.CMAKE_GENERATOR,
70				'-DCMAKE_TOOLCHAIN_FILE=%s' % toolchainFile,
71				'-DANDROID_NDK_HOST_OS=%s' % common.ANDROID_NDK_HOST_OS,
72				'-DANDROID_NDK_PATH=%s' % common.ANDROID_NDK_PATH,
73				'-DANDROID_ABI=%s' % nativeLib.abiVersion,
74				'-DDE_ANDROID_API=%s' % nativeLib.apiVersion,
75				'-DCMAKE_BUILD_TYPE=%s' % buildType,
76				'-DDEQP_TARGET=android',
77				deqpDir
78			], buildDir)
79
80	common.execArgsInDirectory(['cmake', '--build', '.'] + common.EXTRA_BUILD_ARGS, buildDir)
81
82	if not os.path.exists(libsDir):
83		os.makedirs(libsDir)
84
85	shutil.copyfile(srcLibFile, dstLibFile)
86
87	# Copy gdbserver for debugging
88	if buildType.lower() == "debug":
89		srcGdbserverPath = os.path.join(common.ANDROID_NDK_PATH,
90										'prebuilt',
91										nativeLib.prebuiltDir,
92										'gdbserver',
93										'gdbserver')
94		dstGdbserverPath = os.path.join(libsDir, 'gdbserver')
95		shutil.copyfile(srcGdbserverPath, dstGdbserverPath)
96	else:
97		assert not os.path.exists(os.path.join(libsDir, "gdbserver"))
98
99def buildApp (buildRoot, androidBuildType, javaApi):
100	appDir	= os.path.join(buildRoot, "package")
101
102	# Set up app
103	os.chdir(appDir)
104
105	manifestSrcPath = os.path.normpath(os.path.join(common.ANDROID_DIR, "package", "AndroidManifest.xml"))
106	manifestDstPath = os.path.normpath(os.path.join(appDir, "AndroidManifest.xml"))
107
108	# Build dir can be the Android dir, in which case the copy is not needed.
109	if manifestSrcPath != manifestDstPath:
110		shutil.copy(manifestSrcPath, manifestDstPath)
111
112	common.execArgs([
113			common.ANDROID_BIN,
114			'update', 'project',
115			'--name', 'dEQP',
116			'--path', '.',
117			'--target', javaApi,
118		])
119
120	# Build
121	common.execArgs([
122			common.ANT_BIN,
123			androidBuildType,
124			"-Dsource.dir=" + os.path.join(common.ANDROID_DIR, "package", "src"),
125			"-Dresource.absolute.dir=" + os.path.join(common.ANDROID_DIR, "package", "res")
126		])
127
128def signApp (keystore, keyname, storepass, keypass):
129	os.chdir(os.path.join(common.ANDROID_DIR, "package"))
130	common.execArgs([
131			common.JARSIGNER_BIN,
132			'-keystore', keystore,
133			'-storepass', storepass,
134			'-keypass', keypass,
135			'-sigfile', 'CERT',
136			'-digestalg', 'SHA1',
137			'-sigalg', 'MD5withRSA',
138			'-signedjar', 'bin/dEQP-unaligned.apk',
139			'bin/dEQP-release-unsigned.apk',
140			keyname
141		])
142	common.execArgs([
143			common.ZIPALIGN_BIN,
144			'-f', '4',
145			'bin/dEQP-unaligned.apk',
146			'bin/dEQP-release.apk'
147		])
148
149def build (buildRoot=common.ANDROID_DIR, androidBuildType='debug', nativeBuildType="Release", javaApi=common.ANDROID_JAVA_API, doParallelBuild=False):
150	curDir = os.getcwd()
151
152	try:
153		assetsSrcDir = getAssetsDir(buildRoot, common.NATIVE_LIBS[0], nativeBuildType)
154		assetsDstDir = os.path.join(buildRoot, "package", "assets")
155
156		# Remove assets from the first build dir where we copy assets from
157		# to avoid collecting cruft there.
158		if os.path.exists(assetsSrcDir):
159			shutil.rmtree(assetsSrcDir)
160		if os.path.exists(assetsDstDir):
161			shutil.rmtree(assetsDstDir)
162
163		# Remove old libs dir to avoid collecting out-of-date versions
164		# of libs for ABIs not built this time.
165		libTargetDir = os.path.join(buildRoot, "package", "libs")
166		if os.path.exists(libTargetDir):
167			shutil.rmtree(libTargetDir)
168
169		# Build native code
170		nativeBuildArgs = [(buildRoot, libTargetDir, nativeLib, nativeBuildType) for nativeLib in common.NATIVE_LIBS]
171		if doParallelBuild:
172			common.parallelApply(buildNative, nativeBuildArgs)
173		else:
174			common.serialApply(buildNative, nativeBuildArgs)
175
176		# Copy assets
177		if os.path.exists(assetsSrcDir):
178			shutil.copytree(assetsSrcDir, assetsDstDir)
179
180		# Build java code and .apk
181		buildApp(buildRoot, androidBuildType, javaApi)
182
183	finally:
184		# Restore working dir
185		os.chdir(curDir)
186
187def dumpConfig ():
188	print " "
189	for entry in common.CONFIG_STRINGS:
190		print "%-30s : %s" % (entry[0], entry[1])
191	print " "
192
193# Return NDK version as [<major>,<minor>] or None if cannot be figured out.
194def getNdkVersion (path):
195	if path == None:
196		return None
197
198	propFilePath = os.path.join(path, "source.properties")
199	try:
200		with open(propFilePath) as propFile:
201			for line in propFile:
202				keyValue = map(lambda x: string.strip(x), line.split("="))
203				if keyValue[0] == "Pkg.Revision":
204					versionParts = keyValue[1].split(".")
205					return tuple(map(int, versionParts[0:2]))
206	except:
207		print("Could not read source prop file '%s'" % propFilePath)
208
209	return None
210
211def checkConfig ():
212	HOST_OS_TO_DOWNLOAD_STRING = {
213			"linux-x86_64"		: "linux-x86_64",
214			"windows"			: "windows-x86",
215			"windows-x86_64"	: "windows-x86_64"
216		}
217
218	version = getNdkVersion(common.ANDROID_NDK_PATH)
219	# Note: NDK currently maintains compatibility between minor
220	# versions. Error out only on major version mismatch.
221	if version == None or version[0] != common.ANDROID_NDK_VERSION[0]:
222		print("**** WARNING! Deqp requires NDK version %s" % common.ANDROID_NDK_VERSION_STRING)
223		print("**** NDK Path %s does not appear to have that version." % common.ANDROID_NDK_PATH)
224
225		# Download hint will use the version encored in common.py, not
226		# the latest minor version available
227		versionString = common.ANDROID_NDK_VERSION_STRING
228		if common.ANDROID_NDK_HOST_OS in HOST_OS_TO_DOWNLOAD_STRING:
229			osString = HOST_OS_TO_DOWNLOAD_STRING[common.ANDROID_NDK_HOST_OS]
230			print("**** Please install from https://dl.google.com/android/repository/android-ndk-%s-%s.zip" % (versionString, osString))
231		else:
232			print("**** Please download version", versionString, "from https://developer.android.com/ndk/downloads/index.html")
233
234		return False
235
236	return True
237
238if __name__ == "__main__":
239	nativeBuildTypes = ['Release', 'Debug', 'MinSizeRel', 'RelWithAsserts', 'RelWithDebInfo']
240	androidBuildTypes = ['debug', 'release']
241
242	parser = argparse.ArgumentParser()
243	parser.add_argument('--android-build-type', dest='androidBuildType', choices=androidBuildTypes, default='debug', help="Build type for android project..")
244	parser.add_argument('--native-build-type', dest='nativeBuildType', default="RelWithAsserts", choices=nativeBuildTypes, help="Build type passed to cmake when building native code.")
245	parser.add_argument('--build-root', dest='buildRoot', default=common.ANDROID_DIR, help="Root directory for storing build results.")
246	parser.add_argument('--dump-config', dest='dumpConfig', action='store_true', help="Print out all configurations variables")
247	parser.add_argument('--java-api', dest='javaApi', default=common.ANDROID_JAVA_API, help="Set the API signature for the java build.")
248	parser.add_argument('-p', '--parallel-build', dest='parallelBuild', action="store_true", help="Build native libraries in parallel.")
249	parser.add_argument('--skip-config-check', dest='skipConfigCheck', action="store_true", default=False, help="Skips config check. Warranty void.")
250
251	args = parser.parse_args()
252
253	if args.dumpConfig:
254		dumpConfig()
255
256	if not args.skipConfigCheck and not checkConfig():
257		print "Config check failed, exit"
258		exit(-1)
259
260	build(buildRoot=os.path.abspath(args.buildRoot), androidBuildType=args.androidBuildType, nativeBuildType=args.nativeBuildType, javaApi=args.javaApi, doParallelBuild=args.parallelBuild)
261