1# -*- coding: utf-8 -*-
2
3#-------------------------------------------------------------------------
4# drawElements Quality Program utilities
5# --------------------------------------
6#
7# Copyright 2016 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 argparse
25import tempfile
26
27from build.common import *
28from build.build import *
29
30class Environment:
31	def __init__ (self, srcDir, tmpDir):
32		self.srcDir	= srcDir
33		self.tmpDir	= tmpDir
34
35class BuildTestStep:
36	def getName (self):
37		return "<unknown>"
38
39	def isAvailable (self, env):
40		return True
41
42	def run (self, env):
43		raise Exception("Not implemented")
44
45class RunScript(BuildTestStep):
46	def __init__ (self, scriptPath, getExtraArgs = None):
47		self.scriptPath		= scriptPath
48		self.getExtraArgs	= getExtraArgs
49
50	def getName (self):
51		return self.scriptPath
52
53	def run (self, env):
54		args = ["python", os.path.join(env.srcDir, self.scriptPath)]
55
56		if self.getExtraArgs != None:
57			args += self.getExtraArgs(env)
58
59		execute(args)
60
61def makeCflagsArgs (cflags):
62	cflagsStr = " ".join(cflags)
63	return ["-DCMAKE_C_FLAGS=%s" % cflagsStr, "-DCMAKE_CXX_FLAGS=%s" % cflagsStr]
64
65def makeBuildArgs (target, cc, cpp, cflags):
66	return ["-DDEQP_TARGET=%s" % target, "-DCMAKE_C_COMPILER=%s" % cc, "-DCMAKE_CXX_COMPILER=%s" % cpp] + makeCflagsArgs(cflags)
67
68class BuildConfigGen:
69	def isAvailable (self, env):
70		return True
71
72class UnixConfig(BuildConfigGen):
73	def __init__ (self, target, buildType, cc, cpp, cflags):
74		self.target		= target
75		self.buildType	= buildType
76		self.cc			= cc
77		self.cpp		= cpp
78		self.cflags		= cflags
79
80	def isAvailable (self, env):
81		return which(self.cc) != None and which(self.cpp) != None
82
83	def getBuildConfig (self, env, buildDir):
84		args = makeBuildArgs(self.target, self.cc, self.cpp, self.cflags)
85		return BuildConfig(buildDir, self.buildType, args, env.srcDir)
86
87class VSConfig(BuildConfigGen):
88	def __init__ (self, buildType):
89		self.buildType = buildType
90
91	def getBuildConfig (self, env, buildDir):
92		args = ["-DCMAKE_C_FLAGS=/WX -DCMAKE_CXX_FLAGS=/WX"]
93		return BuildConfig(buildDir, self.buildType, args, env.srcDir)
94
95class Build(BuildTestStep):
96	def __init__ (self, buildDir, configGen, generator):
97		self.buildDir	= buildDir
98		self.configGen	= configGen
99		self.generator	= generator
100
101	def getName (self):
102		return self.buildDir
103
104	def isAvailable (self, env):
105		return self.configGen.isAvailable(env) and self.generator != None and self.generator.isAvailable()
106
107	def run (self, env):
108		# specialize config for env
109		buildDir	= os.path.join(env.tmpDir, self.buildDir)
110		curConfig	= self.configGen.getBuildConfig(env, buildDir)
111
112		build(curConfig, self.generator)
113
114class CheckSrcChanges(BuildTestStep):
115	def getName (self):
116		return "check for changes"
117
118	def run (self, env):
119		pushWorkingDir(env.srcDir)
120		execute(["git", "diff", "--exit-code"])
121		popWorkingDir()
122
123def getClangVersion ():
124	knownVersions = ["4.0", "3.9", "3.8", "3.7", "3.6", "3.5"]
125	for version in knownVersions:
126		if which("clang-" + version) != None:
127			return "-" + version
128	return ""
129
130def runSteps (steps):
131	for step in steps:
132		if step.isAvailable(env):
133			print "Run: %s" % step.getName()
134			step.run(env)
135		else:
136			print "Skip: %s" % step.getName()
137
138COMMON_CFLAGS		= ["-Werror", "-Wno-error=unused-function"]
139COMMON_GCC_CFLAGS	= COMMON_CFLAGS + ["-Wno-implicit-fallthrough"]
140COMMON_CLANG_CFLAGS	= COMMON_CFLAGS + ["-Wno-error=unused-command-line-argument"]
141GCC_32BIT_CFLAGS	= COMMON_GCC_CFLAGS + ["-m32"]
142CLANG_32BIT_CFLAGS	= COMMON_CLANG_CFLAGS + ["-m32"]
143GCC_64BIT_CFLAGS	= COMMON_GCC_CFLAGS + ["-m64"]
144CLANG_64BIT_CFLAGS	= COMMON_CLANG_CFLAGS + ["-m64"]
145CLANG_VERSION		= getClangVersion()
146
147# Always ran before any receipe
148PREREQUISITES		= [
149	RunScript(os.path.join("external", "fetch_sources.py"))
150]
151
152# Always ran after any receipe
153POST_CHECKS			= [
154	CheckSrcChanges()
155]
156
157BUILD_TARGETS		= [
158	Build("clang-64-debug",
159		  UnixConfig("null",
160					 "Debug",
161					 "clang" + CLANG_VERSION,
162					 "clang++" + CLANG_VERSION,
163					 CLANG_64BIT_CFLAGS),
164		  ANY_UNIX_GENERATOR),
165	Build("gcc-32-debug",
166		  UnixConfig("null",
167					 "Debug",
168					 "gcc",
169					 "g++",
170					 GCC_32BIT_CFLAGS),
171		  ANY_UNIX_GENERATOR),
172	Build("gcc-64-release",
173		  UnixConfig("null",
174					 "Release",
175					 "gcc",
176					 "g++",
177					 GCC_64BIT_CFLAGS),
178		  ANY_UNIX_GENERATOR),
179	Build("vs-64-debug",
180		  VSConfig("Debug"),
181		  ANY_VS_X64_GENERATOR),
182]
183
184SPECIAL_RECIPES		= [
185	('android-mustpass', [
186			RunScript(os.path.join("scripts", "build_android_mustpass.py"),
187					  lambda env: ["--build-dir", os.path.join(env.tmpDir, "android-mustpass")]),
188		]),
189	('vulkan-mustpass', [
190			RunScript(os.path.join("external", "vulkancts", "scripts", "build_mustpass.py"),
191					  lambda env: ["--build-dir", os.path.join(env.tmpDir, "vulkan-mustpass")]),
192		]),
193	('spirv-binaries', [
194			RunScript(os.path.join("external", "vulkancts", "scripts", "build_spirv_binaries.py"),
195					  lambda env: ["--build-dir", os.path.join(env.tmpDir, "spirv-binaries")]),
196		]),
197	('gen-inl-files', [
198			RunScript(os.path.join("scripts", "gen_egl.py")),
199			RunScript(os.path.join("scripts", "opengl", "gen_all.py")),
200			RunScript(os.path.join("external", "vulkancts", "scripts", "gen_framework.py")),
201			RunScript(os.path.join("scripts", "gen_android_mk.py")),
202			RunScript(os.path.join("scripts", "src_util", "check_all.py")),
203		])
204]
205
206def getBuildRecipes ():
207	return [(b.getName(), [b]) for b in BUILD_TARGETS]
208
209def getAllRecipe (recipes):
210	allSteps = []
211	for name, steps in recipes:
212		allSteps += steps
213	return ("all", allSteps)
214
215def getRecipes ():
216	recipes = getBuildRecipes()
217	recipes += SPECIAL_RECIPES
218	return recipes
219
220def getRecipe (recipes, recipeName):
221	for curName, steps in recipes:
222		if curName == recipeName:
223			return (curName, steps)
224	return None
225
226RECIPES			= getRecipes()
227
228def parseArgs ():
229	parser = argparse.ArgumentParser(description = "Build and test source",
230									 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
231	parser.add_argument("-s",
232						"--src-dir",
233						dest="srcDir",
234						default=DEQP_DIR,
235						help="Source directory")
236	parser.add_argument("-t",
237						"--tmp-dir",
238						dest="tmpDir",
239						default=os.path.join(tempfile.gettempdir(), "deqp-build-test"),
240						help="Temporary directory")
241	parser.add_argument("-r",
242						"--recipe",
243						dest="recipe",
244						choices=[n for n, s in RECIPES] + ["all"],
245						default="all",
246						help="Build / test recipe")
247	parser.add_argument("-d",
248						"--dump-recipes",
249						dest="dumpRecipes",
250						action="store_true",
251						help="Print out recipes that have any available actions")
252	parser.add_argument("--skip-prerequisites",
253						dest="skipPrerequisites",
254						action="store_true",
255						help="Skip external dependency fetch")
256
257	return parser.parse_args()
258
259if __name__ == "__main__":
260	args	= parseArgs()
261	env		= Environment(args.srcDir, args.tmpDir)
262
263	if args.dumpRecipes:
264		for name, steps in RECIPES:
265			for step in steps:
266				if step.isAvailable(env):
267					print name
268					break
269	else:
270		name, steps	= getAllRecipe(RECIPES) if args.recipe == "all" \
271					  else getRecipe(RECIPES, args.recipe)
272
273		print "Running %s" % name
274
275		allSteps = (PREREQUISITES if (args.skipPrerequisites == False) else []) + steps + POST_CHECKS
276		runSteps(allSteps)
277
278		print "All steps completed successfully"
279