• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 copy
27import zlib
28import time
29import shlex
30import shutil
31import fnmatch
32import tarfile
33import argparse
34import platform
35import datetime
36import tempfile
37import posixpath
38import subprocess
39
40from build.common import *
41from build.config import *
42from build.build import *
43
44pythonExecutable = sys.executable or "python"
45
46def die (msg):
47	print(msg)
48	sys.exit(-1)
49
50def removeLeadingPath (path, basePath):
51	# Both inputs must be normalized already
52	assert os.path.normpath(path) == path
53	assert os.path.normpath(basePath) == basePath
54	return path[len(basePath) + 1:]
55
56def findFile (candidates):
57	for file in candidates:
58		if os.path.exists(file):
59			return file
60	return None
61
62def getFileList (basePath):
63	allFiles	= []
64	basePath	= os.path.normpath(basePath)
65	for root, dirs, files in os.walk(basePath):
66		for file in files:
67			relPath = removeLeadingPath(os.path.normpath(os.path.join(root, file)), basePath)
68			allFiles.append(relPath)
69	return allFiles
70
71def toDatetime (dateTuple):
72	Y, M, D = dateTuple
73	return datetime.datetime(Y, M, D)
74
75class PackageBuildInfo:
76	def __init__ (self, releaseConfig, srcBasePath, dstBasePath, tmpBasePath):
77		self.releaseConfig	= releaseConfig
78		self.srcBasePath	= srcBasePath
79		self.dstBasePath	= dstBasePath
80		self.tmpBasePath	= tmpBasePath
81
82	def getReleaseConfig (self):
83		return self.releaseConfig
84
85	def getReleaseVersion (self):
86		return self.releaseConfig.getVersion()
87
88	def getReleaseId (self):
89		# Release id is crc32(releaseConfig + release)
90		return zlib.crc32(self.releaseConfig.getName() + self.releaseConfig.getVersion()) & 0xffffffff
91
92	def getSrcBasePath (self):
93		return self.srcBasePath
94
95	def getTmpBasePath (self):
96		return self.tmpBasePath
97
98class DstFile (object):
99	def __init__ (self, dstFile):
100		self.dstFile = dstFile
101
102	def makeDir (self):
103		dirName = os.path.dirname(self.dstFile)
104		if not os.path.exists(dirName):
105			os.makedirs(dirName)
106
107	def make (self, packageBuildInfo):
108		assert False # Should not be called
109
110class CopyFile (DstFile):
111	def __init__ (self, srcFile, dstFile):
112		super(CopyFile, self).__init__(dstFile)
113		self.srcFile = srcFile
114
115	def make (self, packageBuildInfo):
116		self.makeDir()
117		if os.path.exists(self.dstFile):
118			die("%s already exists" % self.dstFile)
119		shutil.copyfile(self.srcFile, self.dstFile)
120
121class GenReleaseInfoFileTarget (DstFile):
122	def __init__ (self, dstFile):
123		super(GenReleaseInfoFileTarget, self).__init__(dstFile)
124
125	def make (self, packageBuildInfo):
126		self.makeDir()
127
128		scriptPath = os.path.normpath(os.path.join(packageBuildInfo.srcBasePath, "framework", "qphelper", "gen_release_info.py"))
129		execute([
130				pythonExecutable,
131				"-B", # no .py[co]
132				scriptPath,
133				"--name=%s" % packageBuildInfo.getReleaseVersion(),
134				"--id=0x%08x" % packageBuildInfo.getReleaseId(),
135				"--out=%s" % self.dstFile
136			])
137
138class GenCMake (DstFile):
139	def __init__ (self, srcFile, dstFile, replaceVars):
140		super(GenCMake, self).__init__(dstFile)
141		self.srcFile		= srcFile
142		self.replaceVars	= replaceVars
143
144	def make (self, packageBuildInfo):
145		self.makeDir()
146		print("    GenCMake: %s" % removeLeadingPath(self.dstFile, packageBuildInfo.dstBasePath))
147		src = readFile(self.srcFile)
148		for var, value in self.replaceVars:
149			src = re.sub('set\(%s\s+"[^"]*"' % re.escape(var),
150						 'set(%s "%s"' % (var, value), src)
151		writeFile(self.dstFile, src)
152
153def createFileTargets (srcBasePath, dstBasePath, files, filters):
154	usedFiles	= set() # Files that are already included by other filters
155	targets		= []
156
157	for isMatch, createFileObj in filters:
158		# Build list of files that match filter
159		matchingFiles = []
160		for file in files:
161			if not file in usedFiles and isMatch(file):
162				matchingFiles.append(file)
163
164		# Build file objects, add to used set
165		for file in matchingFiles:
166			usedFiles.add(file)
167			targets.append(createFileObj(os.path.join(srcBasePath, file), os.path.join(dstBasePath, file)))
168
169	return targets
170
171# Generates multiple file targets based on filters
172class FileTargetGroup:
173	def __init__ (self, srcBasePath, dstBasePath, filters, srcBasePathFunc=PackageBuildInfo.getSrcBasePath):
174		self.srcBasePath	= srcBasePath
175		self.dstBasePath	= dstBasePath
176		self.filters		= filters
177		self.getSrcBasePath	= srcBasePathFunc
178
179	def make (self, packageBuildInfo):
180		fullSrcPath		= os.path.normpath(os.path.join(self.getSrcBasePath(packageBuildInfo), self.srcBasePath))
181		fullDstPath		= os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstBasePath))
182
183		allFiles		= getFileList(fullSrcPath)
184		targets			= createFileTargets(fullSrcPath, fullDstPath, allFiles, self.filters)
185
186		# Make all file targets
187		for file in targets:
188			file.make(packageBuildInfo)
189
190# Single file target
191class SingleFileTarget:
192	def __init__ (self, srcFile, dstFile, makeTarget):
193		self.srcFile	= srcFile
194		self.dstFile	= dstFile
195		self.makeTarget	= makeTarget
196
197	def make (self, packageBuildInfo):
198		fullSrcPath		= os.path.normpath(os.path.join(packageBuildInfo.srcBasePath, self.srcFile))
199		fullDstPath		= os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstFile))
200
201		target = self.makeTarget(fullSrcPath, fullDstPath)
202		target.make(packageBuildInfo)
203
204class BuildTarget:
205	def __init__ (self, baseConfig, generator, targets = None):
206		self.baseConfig	= baseConfig
207		self.generator	= generator
208		self.targets	= targets
209
210	def make (self, packageBuildInfo):
211		print("    Building %s" % self.baseConfig.getBuildDir())
212
213		# Create config with full build dir path
214		config = BuildConfig(os.path.join(packageBuildInfo.getTmpBasePath(), self.baseConfig.getBuildDir()),
215							 self.baseConfig.getBuildType(),
216							 self.baseConfig.getArgs(),
217							 srcPath = os.path.join(packageBuildInfo.dstBasePath, "src"))
218
219		assert not os.path.exists(config.getBuildDir())
220		build(config, self.generator, self.targets)
221
222class BuildAndroidTarget:
223	def __init__ (self, dstFile):
224		self.dstFile = dstFile
225
226	def make (self, packageBuildInfo):
227		print("    Building Android binary")
228
229		buildRoot = os.path.join(packageBuildInfo.tmpBasePath, "android-build")
230
231		assert not os.path.exists(buildRoot)
232		os.makedirs(buildRoot)
233
234		# Execute build script
235		scriptPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, "src", "android", "scripts", "build.py"))
236		execute([
237				pythonExecutable,
238				"-B", # no .py[co]
239				scriptPath,
240				"--build-root=%s" % buildRoot,
241			])
242
243		srcFile		= os.path.normpath(os.path.join(buildRoot, "package", "bin", "dEQP-debug.apk"))
244		dstFile		= os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstFile))
245
246		CopyFile(srcFile, dstFile).make(packageBuildInfo)
247
248class FetchExternalSourcesTarget:
249	def __init__ (self):
250		pass
251
252	def make (self, packageBuildInfo):
253		scriptPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, "src", "external", "fetch_sources.py"))
254		execute([
255				pythonExecutable,
256				"-B", # no .py[co]
257				scriptPath,
258			])
259
260class RemoveSourcesTarget:
261	def __init__ (self):
262		pass
263
264	def make (self, packageBuildInfo):
265		shutil.rmtree(os.path.join(packageBuildInfo.dstBasePath, "src"), ignore_errors=False)
266
267class Module:
268	def __init__ (self, name, targets):
269		self.name		= name
270		self.targets	= targets
271
272	def make (self, packageBuildInfo):
273		for target in self.targets:
274			target.make(packageBuildInfo)
275
276class ReleaseConfig:
277	def __init__ (self, name, version, modules, sources = True):
278		self.name			= name
279		self.version		= version
280		self.modules		= modules
281		self.sources		= sources
282
283	def getName (self):
284		return self.name
285
286	def getVersion (self):
287		return self.version
288
289	def getModules (self):
290		return self.modules
291
292	def packageWithSources (self):
293		return self.sources
294
295def matchIncludeExclude (includePatterns, excludePatterns, filename):
296	components = os.path.normpath(filename).split(os.sep)
297	for pattern in excludePatterns:
298		for component in components:
299			if fnmatch.fnmatch(component, pattern):
300				return False
301
302	for pattern in includePatterns:
303		for component in components:
304			if fnmatch.fnmatch(component, pattern):
305				return True
306
307	return False
308
309def copyFileFilter (includePatterns, excludePatterns=[]):
310	return (lambda f: matchIncludeExclude(includePatterns, excludePatterns, f),
311			lambda s, d: CopyFile(s, d))
312
313def makeFileCopyGroup (srcDir, dstDir, includePatterns, excludePatterns=[]):
314	return FileTargetGroup(srcDir, dstDir, [copyFileFilter(includePatterns, excludePatterns)])
315
316def makeTmpFileCopyGroup (srcDir, dstDir, includePatterns, excludePatterns=[]):
317	return FileTargetGroup(srcDir, dstDir, [copyFileFilter(includePatterns, excludePatterns)], PackageBuildInfo.getTmpBasePath)
318
319def makeFileCopy (srcFile, dstFile):
320	return SingleFileTarget(srcFile, dstFile, lambda s, d: CopyFile(s, d))
321
322def getReleaseFileName (configName, releaseName):
323	today = datetime.date.today()
324	return "dEQP-%s-%04d-%02d-%02d-%s" % (releaseName, today.year, today.month, today.day, configName)
325
326def getTempDir ():
327	dirName = os.path.join(tempfile.gettempdir(), "dEQP-Releases")
328	if not os.path.exists(dirName):
329		os.makedirs(dirName)
330	return dirName
331
332def makeRelease (releaseConfig):
333	releaseName			= getReleaseFileName(releaseConfig.getName(), releaseConfig.getVersion())
334	tmpPath				= getTempDir()
335	srcBasePath			= DEQP_DIR
336	dstBasePath			= os.path.join(tmpPath, releaseName)
337	tmpBasePath			= os.path.join(tmpPath, releaseName + "-tmp")
338	packageBuildInfo	= PackageBuildInfo(releaseConfig, srcBasePath, dstBasePath, tmpBasePath)
339	dstArchiveName		= releaseName + ".tar.bz2"
340
341	print("Creating release %s to %s" % (releaseName, tmpPath))
342
343	# Remove old temporary dirs
344	for path in [dstBasePath, tmpBasePath]:
345		if os.path.exists(path):
346			shutil.rmtree(path, ignore_errors=False)
347
348	# Make all modules
349	for module in releaseConfig.getModules():
350		print("  Processing module %s" % module.name)
351		module.make(packageBuildInfo)
352
353	# Remove sources?
354	if not releaseConfig.packageWithSources():
355		shutil.rmtree(os.path.join(dstBasePath, "src"), ignore_errors=False)
356
357	# Create archive
358	print("Creating %s" % dstArchiveName)
359	archive	= tarfile.open(dstArchiveName, 'w:bz2')
360	archive.add(dstBasePath, arcname=releaseName)
361	archive.close()
362
363	# Remove tmp dirs
364	for path in [dstBasePath, tmpBasePath]:
365		if os.path.exists(path):
366			shutil.rmtree(path, ignore_errors=False)
367
368	print("Done!")
369
370# Module declarations
371
372SRC_FILE_PATTERNS	= ["*.h", "*.hpp", "*.c", "*.cpp", "*.m", "*.mm", "*.inl", "*.java", "*.aidl", "CMakeLists.txt", "LICENSE.txt", "*.cmake"]
373TARGET_PATTERNS		= ["*.cmake", "*.h", "*.lib", "*.dll", "*.so", "*.txt"]
374
375BASE = Module("Base", [
376	makeFileCopy		("LICENSE",									"src/LICENSE"),
377	makeFileCopy		("CMakeLists.txt",							"src/CMakeLists.txt"),
378	makeFileCopyGroup	("targets",									"src/targets",							TARGET_PATTERNS),
379	makeFileCopyGroup	("execserver",								"src/execserver",						SRC_FILE_PATTERNS),
380	makeFileCopyGroup	("executor",								"src/executor",							SRC_FILE_PATTERNS),
381	makeFileCopy		("modules/CMakeLists.txt",					"src/modules/CMakeLists.txt"),
382	makeFileCopyGroup	("external",								"src/external",							["CMakeLists.txt", "*.py"]),
383
384	# Stylesheet for displaying test logs on browser
385	makeFileCopyGroup	("doc/testlog-stylesheet",					"doc/testlog-stylesheet",				["*"]),
386
387	# Non-optional parts of framework
388	makeFileCopy		("framework/CMakeLists.txt",				"src/framework/CMakeLists.txt"),
389	makeFileCopyGroup	("framework/delibs",						"src/framework/delibs",					SRC_FILE_PATTERNS),
390	makeFileCopyGroup	("framework/common",						"src/framework/common",					SRC_FILE_PATTERNS),
391	makeFileCopyGroup	("framework/qphelper",						"src/framework/qphelper",				SRC_FILE_PATTERNS),
392	makeFileCopyGroup	("framework/platform",						"src/framework/platform",				SRC_FILE_PATTERNS),
393	makeFileCopyGroup	("framework/opengl",						"src/framework/opengl",					SRC_FILE_PATTERNS, ["simplereference"]),
394	makeFileCopyGroup	("framework/egl",							"src/framework/egl",					SRC_FILE_PATTERNS),
395
396	# android sources
397	makeFileCopyGroup	("android/package/src",						"src/android/package/src",				SRC_FILE_PATTERNS),
398	makeFileCopy		("android/package/AndroidManifest.xml",		"src/android/package/AndroidManifest.xml"),
399	makeFileCopyGroup	("android/package/res",						"src/android/package/res",				["*.png", "*.xml"]),
400	makeFileCopyGroup	("android/scripts",							"src/android/scripts", [
401		"common.py",
402		"build.py",
403		"resources.py",
404		"install.py",
405		"launch.py",
406		"debug.py"
407		]),
408
409	# Release info
410	GenReleaseInfoFileTarget("src/framework/qphelper/qpReleaseInfo.inl")
411])
412
413DOCUMENTATION = Module("Documentation", [
414	makeFileCopyGroup	("doc/pdf",									"doc",									["*.pdf"]),
415	makeFileCopyGroup	("doc",										"doc",									["porting_layer_changes_*.txt"]),
416])
417
418GLSHARED = Module("Shared GL Tests", [
419	# Optional framework components
420	makeFileCopyGroup	("framework/randomshaders",					"src/framework/randomshaders",			SRC_FILE_PATTERNS),
421	makeFileCopyGroup	("framework/opengl/simplereference",		"src/framework/opengl/simplereference",	SRC_FILE_PATTERNS),
422	makeFileCopyGroup	("framework/referencerenderer",				"src/framework/referencerenderer",		SRC_FILE_PATTERNS),
423
424	makeFileCopyGroup	("modules/glshared",						"src/modules/glshared",					SRC_FILE_PATTERNS),
425])
426
427GLES2 = Module("GLES2", [
428	makeFileCopyGroup	("modules/gles2",							"src/modules/gles2",					SRC_FILE_PATTERNS),
429	makeFileCopyGroup	("data/gles2",								"src/data/gles2",						["*.*"]),
430	makeFileCopyGroup	("doc/testspecs/GLES2",						"doc/testspecs/GLES2",					["*.txt"])
431])
432
433GLES3 = Module("GLES3", [
434	makeFileCopyGroup	("modules/gles3",							"src/modules/gles3",					SRC_FILE_PATTERNS),
435	makeFileCopyGroup	("data/gles3",								"src/data/gles3",						["*.*"]),
436	makeFileCopyGroup	("doc/testspecs/GLES3",						"doc/testspecs/GLES3",					["*.txt"])
437])
438
439GLES31 = Module("GLES31", [
440	makeFileCopyGroup	("modules/gles31",							"src/modules/gles31",					SRC_FILE_PATTERNS),
441	makeFileCopyGroup	("data/gles31",								"src/data/gles31",						["*.*"]),
442	makeFileCopyGroup	("doc/testspecs/GLES31",					"doc/testspecs/GLES31",					["*.txt"])
443])
444
445EGL = Module("EGL", [
446	makeFileCopyGroup	("modules/egl",								"src/modules/egl",						SRC_FILE_PATTERNS)
447])
448
449INTERNAL = Module("Internal", [
450	makeFileCopyGroup	("modules/internal",						"src/modules/internal",					SRC_FILE_PATTERNS),
451	makeFileCopyGroup	("data/internal",							"src/data/internal",					["*.*"]),
452])
453
454EXTERNAL_SRCS = Module("External sources", [
455	FetchExternalSourcesTarget()
456])
457
458ANDROID_BINARIES = Module("Android Binaries", [
459	BuildAndroidTarget	("bin/android/dEQP.apk"),
460	makeFileCopyGroup	("targets/android",							"bin/android",							["*.bat", "*.sh"]),
461])
462
463COMMON_BUILD_ARGS	= ['-DPNG_SRC_PATH=%s' % os.path.realpath(os.path.join(DEQP_DIR, '..', 'libpng'))]
464NULL_X32_CONFIG		= BuildConfig('null-x32',	'Release', ['-DDEQP_TARGET=null', '-DCMAKE_C_FLAGS=-m32', '-DCMAKE_CXX_FLAGS=-m32'] + COMMON_BUILD_ARGS)
465NULL_X64_CONFIG		= BuildConfig('null-x64',	'Release', ['-DDEQP_TARGET=null', '-DCMAKE_C_FLAGS=-m64', '-DCMAKE_CXX_FLAGS=-m64'] + COMMON_BUILD_ARGS)
466GLX_X32_CONFIG		= BuildConfig('glx-x32',	'Release', ['-DDEQP_TARGET=x11_glx', '-DCMAKE_C_FLAGS=-m32', '-DCMAKE_CXX_FLAGS=-m32'] + COMMON_BUILD_ARGS)
467GLX_X64_CONFIG		= BuildConfig('glx-x64',	'Release', ['-DDEQP_TARGET=x11_glx', '-DCMAKE_C_FLAGS=-m64', '-DCMAKE_CXX_FLAGS=-m64'] + COMMON_BUILD_ARGS)
468
469EXCLUDE_BUILD_FILES = ["CMakeFiles", "*.a", "*.cmake"]
470
471LINUX_X32_COMMON_BINARIES = Module("Linux x32 Common Binaries", [
472	BuildTarget			(NULL_X32_CONFIG, ANY_UNIX_GENERATOR),
473	makeTmpFileCopyGroup(NULL_X32_CONFIG.getBuildDir() + "/execserver",		"bin/linux32",					["*"],	EXCLUDE_BUILD_FILES),
474	makeTmpFileCopyGroup(NULL_X32_CONFIG.getBuildDir() + "/executor",		"bin/linux32",					["*"],	EXCLUDE_BUILD_FILES),
475])
476
477LINUX_X64_COMMON_BINARIES = Module("Linux x64 Common Binaries", [
478	BuildTarget			(NULL_X64_CONFIG, ANY_UNIX_GENERATOR),
479	makeTmpFileCopyGroup(NULL_X64_CONFIG.getBuildDir() + "/execserver",		"bin/linux64",					["*"],	EXCLUDE_BUILD_FILES),
480	makeTmpFileCopyGroup(NULL_X64_CONFIG.getBuildDir() + "/executor",		"bin/linux64",					["*"],	EXCLUDE_BUILD_FILES),
481])
482
483# Special module to remove src dir, for example after binary build
484REMOVE_SOURCES = Module("Remove sources from package", [
485	RemoveSourcesTarget()
486])
487
488# Release configuration
489
490ALL_MODULES		= [
491	BASE,
492	DOCUMENTATION,
493	GLSHARED,
494	GLES2,
495	GLES3,
496	GLES31,
497	EGL,
498	INTERNAL,
499	EXTERNAL_SRCS,
500]
501
502ALL_BINARIES	= [
503	LINUX_X64_COMMON_BINARIES,
504	ANDROID_BINARIES,
505]
506
507RELEASE_CONFIGS	= {
508	"src":		ALL_MODULES,
509	"src-bin":	ALL_MODULES + ALL_BINARIES,
510	"bin":		ALL_MODULES + ALL_BINARIES + [REMOVE_SOURCES],
511}
512
513def parseArgs ():
514	parser = argparse.ArgumentParser(description = "Build release package")
515	parser.add_argument("-c",
516						"--config",
517						dest="config",
518						choices=RELEASE_CONFIGS.keys(),
519						required=True,
520						help="Release configuration")
521	parser.add_argument("-n",
522						"--name",
523						dest="name",
524						required=True,
525						help="Package-specific name")
526	parser.add_argument("-v",
527						"--version",
528						dest="version",
529						required=True,
530						help="Version code")
531	return parser.parse_args()
532
533if __name__ == "__main__":
534	args	= parseArgs()
535	config	= ReleaseConfig(args.name, args.version, RELEASE_CONFIGS[args.config])
536	makeRelease(config)
537