# -*- coding: utf-8 -*- #------------------------------------------------------------------------- # drawElements Quality Program utilities # -------------------------------------- # # Copyright 2015 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. # #------------------------------------------------------------------------- import os import re import sys import copy import zlib import time import shlex import shutil import fnmatch import tarfile import argparse import platform import datetime import tempfile import posixpath import subprocess from ctsbuild.common import * from ctsbuild.config import * from ctsbuild.build import * pythonExecutable = sys.executable or "python" def die (msg): print(msg) sys.exit(-1) def removeLeadingPath (path, basePath): # Both inputs must be normalized already assert os.path.normpath(path) == path assert os.path.normpath(basePath) == basePath return path[len(basePath) + 1:] def findFile (candidates): for file in candidates: if os.path.exists(file): return file return None def getFileList (basePath): allFiles = [] basePath = os.path.normpath(basePath) for root, dirs, files in os.walk(basePath): for file in files: relPath = removeLeadingPath(os.path.normpath(os.path.join(root, file)), basePath) allFiles.append(relPath) return allFiles def toDatetime (dateTuple): Y, M, D = dateTuple return datetime.datetime(Y, M, D) class PackageBuildInfo: def __init__ (self, releaseConfig, srcBasePath, dstBasePath, tmpBasePath): self.releaseConfig = releaseConfig self.srcBasePath = srcBasePath self.dstBasePath = dstBasePath self.tmpBasePath = tmpBasePath def getReleaseConfig (self): return self.releaseConfig def getReleaseVersion (self): return self.releaseConfig.getVersion() def getReleaseId (self): # Release id is crc32(releaseConfig + release) return zlib.crc32(self.releaseConfig.getName() + self.releaseConfig.getVersion()) & 0xffffffff def getSrcBasePath (self): return self.srcBasePath def getTmpBasePath (self): return self.tmpBasePath class DstFile (object): def __init__ (self, dstFile): self.dstFile = dstFile def makeDir (self): dirName = os.path.dirname(self.dstFile) if not os.path.exists(dirName): os.makedirs(dirName) def make (self, packageBuildInfo): assert False # Should not be called class CopyFile (DstFile): def __init__ (self, srcFile, dstFile): super(CopyFile, self).__init__(dstFile) self.srcFile = srcFile def make (self, packageBuildInfo): self.makeDir() if os.path.exists(self.dstFile): die("%s already exists" % self.dstFile) shutil.copyfile(self.srcFile, self.dstFile) class GenReleaseInfoFileTarget (DstFile): def __init__ (self, dstFile): super(GenReleaseInfoFileTarget, self).__init__(dstFile) def make (self, packageBuildInfo): self.makeDir() scriptPath = os.path.normpath(os.path.join(packageBuildInfo.srcBasePath, "framework", "qphelper", "gen_release_info.py")) execute([ pythonExecutable, "-B", # no .py[co] scriptPath, "--name=%s" % packageBuildInfo.getReleaseVersion(), "--id=0x%08x" % packageBuildInfo.getReleaseId(), "--out=%s" % self.dstFile ]) class GenCMake (DstFile): def __init__ (self, srcFile, dstFile, replaceVars): super(GenCMake, self).__init__(dstFile) self.srcFile = srcFile self.replaceVars = replaceVars def make (self, packageBuildInfo): self.makeDir() print(" GenCMake: %s" % removeLeadingPath(self.dstFile, packageBuildInfo.dstBasePath)) src = readFile(self.srcFile) for var, value in self.replaceVars: src = re.sub('set\(%s\s+"[^"]*"' % re.escape(var), 'set(%s "%s"' % (var, value), src) writeFile(self.dstFile, src) def createFileTargets (srcBasePath, dstBasePath, files, filters): usedFiles = set() # Files that are already included by other filters targets = [] for isMatch, createFileObj in filters: # Build list of files that match filter matchingFiles = [] for file in files: if not file in usedFiles and isMatch(file): matchingFiles.append(file) # Build file objects, add to used set for file in matchingFiles: usedFiles.add(file) targets.append(createFileObj(os.path.join(srcBasePath, file), os.path.join(dstBasePath, file))) return targets # Generates multiple file targets based on filters class FileTargetGroup: def __init__ (self, srcBasePath, dstBasePath, filters, srcBasePathFunc=PackageBuildInfo.getSrcBasePath): self.srcBasePath = srcBasePath self.dstBasePath = dstBasePath self.filters = filters self.getSrcBasePath = srcBasePathFunc def make (self, packageBuildInfo): fullSrcPath = os.path.normpath(os.path.join(self.getSrcBasePath(packageBuildInfo), self.srcBasePath)) fullDstPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstBasePath)) allFiles = getFileList(fullSrcPath) targets = createFileTargets(fullSrcPath, fullDstPath, allFiles, self.filters) # Make all file targets for file in targets: file.make(packageBuildInfo) # Single file target class SingleFileTarget: def __init__ (self, srcFile, dstFile, makeTarget): self.srcFile = srcFile self.dstFile = dstFile self.makeTarget = makeTarget def make (self, packageBuildInfo): fullSrcPath = os.path.normpath(os.path.join(packageBuildInfo.srcBasePath, self.srcFile)) fullDstPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstFile)) target = self.makeTarget(fullSrcPath, fullDstPath) target.make(packageBuildInfo) class BuildTarget: def __init__ (self, baseConfig, generator, targets = None): self.baseConfig = baseConfig self.generator = generator self.targets = targets def make (self, packageBuildInfo): print(" Building %s" % self.baseConfig.getBuildDir()) # Create config with full build dir path config = BuildConfig(os.path.join(packageBuildInfo.getTmpBasePath(), self.baseConfig.getBuildDir()), self.baseConfig.getBuildType(), self.baseConfig.getArgs(), srcPath = os.path.join(packageBuildInfo.dstBasePath, "src")) assert not os.path.exists(config.getBuildDir()) build(config, self.generator, self.targets) class BuildAndroidTarget: def __init__ (self, dstFile): self.dstFile = dstFile def make (self, packageBuildInfo): print(" Building Android binary") buildRoot = os.path.join(packageBuildInfo.tmpBasePath, "android-build") assert not os.path.exists(buildRoot) os.makedirs(buildRoot) # Execute build script scriptPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, "src", "android", "scripts", "build.py")) execute([ pythonExecutable, "-B", # no .py[co] scriptPath, "--build-root=%s" % buildRoot, ]) srcFile = os.path.normpath(os.path.join(buildRoot, "package", "bin", "dEQP-debug.apk")) dstFile = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, self.dstFile)) CopyFile(srcFile, dstFile).make(packageBuildInfo) class FetchExternalSourcesTarget: def __init__ (self): pass def make (self, packageBuildInfo): scriptPath = os.path.normpath(os.path.join(packageBuildInfo.dstBasePath, "src", "external", "fetch_sources.py")) execute([ pythonExecutable, "-B", # no .py[co] scriptPath, ]) class RemoveSourcesTarget: def __init__ (self): pass def make (self, packageBuildInfo): shutil.rmtree(os.path.join(packageBuildInfo.dstBasePath, "src"), ignore_errors=False) class Module: def __init__ (self, name, targets): self.name = name self.targets = targets def make (self, packageBuildInfo): for target in self.targets: target.make(packageBuildInfo) class ReleaseConfig: def __init__ (self, name, version, modules, sources = True): self.name = name self.version = version self.modules = modules self.sources = sources def getName (self): return self.name def getVersion (self): return self.version def getModules (self): return self.modules def packageWithSources (self): return self.sources def matchIncludeExclude (includePatterns, excludePatterns, filename): components = os.path.normpath(filename).split(os.sep) for pattern in excludePatterns: for component in components: if fnmatch.fnmatch(component, pattern): return False for pattern in includePatterns: for component in components: if fnmatch.fnmatch(component, pattern): return True return False def copyFileFilter (includePatterns, excludePatterns=[]): return (lambda f: matchIncludeExclude(includePatterns, excludePatterns, f), lambda s, d: CopyFile(s, d)) def makeFileCopyGroup (srcDir, dstDir, includePatterns, excludePatterns=[]): return FileTargetGroup(srcDir, dstDir, [copyFileFilter(includePatterns, excludePatterns)]) def makeTmpFileCopyGroup (srcDir, dstDir, includePatterns, excludePatterns=[]): return FileTargetGroup(srcDir, dstDir, [copyFileFilter(includePatterns, excludePatterns)], PackageBuildInfo.getTmpBasePath) def makeFileCopy (srcFile, dstFile): return SingleFileTarget(srcFile, dstFile, lambda s, d: CopyFile(s, d)) def getReleaseFileName (configName, releaseName): today = datetime.date.today() return "dEQP-%s-%04d-%02d-%02d-%s" % (releaseName, today.year, today.month, today.day, configName) def getTempDir (): dirName = os.path.join(tempfile.gettempdir(), "dEQP-Releases") if not os.path.exists(dirName): os.makedirs(dirName) return dirName def makeRelease (releaseConfig): releaseName = getReleaseFileName(releaseConfig.getName(), releaseConfig.getVersion()) tmpPath = getTempDir() srcBasePath = DEQP_DIR dstBasePath = os.path.join(tmpPath, releaseName) tmpBasePath = os.path.join(tmpPath, releaseName + "-tmp") packageBuildInfo = PackageBuildInfo(releaseConfig, srcBasePath, dstBasePath, tmpBasePath) dstArchiveName = releaseName + ".tar.bz2" print("Creating release %s to %s" % (releaseName, tmpPath)) # Remove old temporary dirs for path in [dstBasePath, tmpBasePath]: if os.path.exists(path): shutil.rmtree(path, ignore_errors=False) # Make all modules for module in releaseConfig.getModules(): print(" Processing module %s" % module.name) module.make(packageBuildInfo) # Remove sources? if not releaseConfig.packageWithSources(): shutil.rmtree(os.path.join(dstBasePath, "src"), ignore_errors=False) # Create archive print("Creating %s" % dstArchiveName) archive = tarfile.open(dstArchiveName, 'w:bz2') archive.add(dstBasePath, arcname=releaseName) archive.close() # Remove tmp dirs for path in [dstBasePath, tmpBasePath]: if os.path.exists(path): shutil.rmtree(path, ignore_errors=False) print("Done!") # Module declarations SRC_FILE_PATTERNS = ["*.h", "*.hpp", "*.c", "*.cpp", "*.m", "*.mm", "*.inl", "*.java", "*.aidl", "CMakeLists.txt", "LICENSE.txt", "*.cmake"] TARGET_PATTERNS = ["*.cmake", "*.h", "*.lib", "*.dll", "*.so", "*.txt"] BASE = Module("Base", [ makeFileCopy ("LICENSE", "src/LICENSE"), makeFileCopy ("CMakeLists.txt", "src/CMakeLists.txt"), makeFileCopyGroup ("targets", "src/targets", TARGET_PATTERNS), makeFileCopyGroup ("execserver", "src/execserver", SRC_FILE_PATTERNS), makeFileCopyGroup ("executor", "src/executor", SRC_FILE_PATTERNS), makeFileCopy ("modules/CMakeLists.txt", "src/modules/CMakeLists.txt"), makeFileCopyGroup ("external", "src/external", ["CMakeLists.txt", "*.py"]), # Stylesheet for displaying test logs on browser makeFileCopyGroup ("doc/testlog-stylesheet", "doc/testlog-stylesheet", ["*"]), # Non-optional parts of framework makeFileCopy ("framework/CMakeLists.txt", "src/framework/CMakeLists.txt"), makeFileCopyGroup ("framework/delibs", "src/framework/delibs", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/common", "src/framework/common", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/qphelper", "src/framework/qphelper", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/platform", "src/framework/platform", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/opengl", "src/framework/opengl", SRC_FILE_PATTERNS, ["simplereference"]), makeFileCopyGroup ("framework/egl", "src/framework/egl", SRC_FILE_PATTERNS), # android sources makeFileCopyGroup ("android/package/src", "src/android/package/src", SRC_FILE_PATTERNS), makeFileCopy ("android/package/AndroidManifest.xml", "src/android/package/AndroidManifest.xml"), makeFileCopyGroup ("android/package/res", "src/android/package/res", ["*.png", "*.xml"]), makeFileCopyGroup ("android/scripts", "src/android/scripts", [ "common.py", "build.py", "resources.py", "install.py", "launch.py", "debug.py" ]), # Release info GenReleaseInfoFileTarget("src/framework/qphelper/qpReleaseInfo.inl") ]) DOCUMENTATION = Module("Documentation", [ makeFileCopyGroup ("doc/pdf", "doc", ["*.pdf"]), makeFileCopyGroup ("doc", "doc", ["porting_layer_changes_*.txt"]), ]) GLSHARED = Module("Shared GL Tests", [ # Optional framework components makeFileCopyGroup ("framework/randomshaders", "src/framework/randomshaders", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/opengl/simplereference", "src/framework/opengl/simplereference", SRC_FILE_PATTERNS), makeFileCopyGroup ("framework/referencerenderer", "src/framework/referencerenderer", SRC_FILE_PATTERNS), makeFileCopyGroup ("modules/glshared", "src/modules/glshared", SRC_FILE_PATTERNS), ]) GLES2 = Module("GLES2", [ makeFileCopyGroup ("modules/gles2", "src/modules/gles2", SRC_FILE_PATTERNS), makeFileCopyGroup ("data/gles2", "src/data/gles2", ["*.*"]), makeFileCopyGroup ("doc/testspecs/GLES2", "doc/testspecs/GLES2", ["*.txt"]) ]) GLES3 = Module("GLES3", [ makeFileCopyGroup ("modules/gles3", "src/modules/gles3", SRC_FILE_PATTERNS), makeFileCopyGroup ("data/gles3", "src/data/gles3", ["*.*"]), makeFileCopyGroup ("doc/testspecs/GLES3", "doc/testspecs/GLES3", ["*.txt"]) ]) GLES31 = Module("GLES31", [ makeFileCopyGroup ("modules/gles31", "src/modules/gles31", SRC_FILE_PATTERNS), makeFileCopyGroup ("data/gles31", "src/data/gles31", ["*.*"]), makeFileCopyGroup ("doc/testspecs/GLES31", "doc/testspecs/GLES31", ["*.txt"]) ]) EGL = Module("EGL", [ makeFileCopyGroup ("modules/egl", "src/modules/egl", SRC_FILE_PATTERNS) ]) INTERNAL = Module("Internal", [ makeFileCopyGroup ("modules/internal", "src/modules/internal", SRC_FILE_PATTERNS), makeFileCopyGroup ("data/internal", "src/data/internal", ["*.*"]), ]) EXTERNAL_SRCS = Module("External sources", [ FetchExternalSourcesTarget() ]) ANDROID_BINARIES = Module("Android Binaries", [ BuildAndroidTarget ("bin/android/dEQP.apk"), makeFileCopyGroup ("targets/android", "bin/android", ["*.bat", "*.sh"]), ]) COMMON_BUILD_ARGS = ['-DPNG_SRC_PATH=%s' % os.path.realpath(os.path.join(DEQP_DIR, '..', 'libpng'))] NULL_X32_CONFIG = BuildConfig('null-x32', 'Release', ['-DDEQP_TARGET=null', '-DCMAKE_C_FLAGS=-m32', '-DCMAKE_CXX_FLAGS=-m32'] + COMMON_BUILD_ARGS) NULL_X64_CONFIG = BuildConfig('null-x64', 'Release', ['-DDEQP_TARGET=null', '-DCMAKE_C_FLAGS=-m64', '-DCMAKE_CXX_FLAGS=-m64'] + COMMON_BUILD_ARGS) GLX_X32_CONFIG = BuildConfig('glx-x32', 'Release', ['-DDEQP_TARGET=x11_glx', '-DCMAKE_C_FLAGS=-m32', '-DCMAKE_CXX_FLAGS=-m32'] + COMMON_BUILD_ARGS) GLX_X64_CONFIG = BuildConfig('glx-x64', 'Release', ['-DDEQP_TARGET=x11_glx', '-DCMAKE_C_FLAGS=-m64', '-DCMAKE_CXX_FLAGS=-m64'] + COMMON_BUILD_ARGS) EXCLUDE_BUILD_FILES = ["CMakeFiles", "*.a", "*.cmake"] LINUX_X32_COMMON_BINARIES = Module("Linux x32 Common Binaries", [ BuildTarget (NULL_X32_CONFIG, ANY_UNIX_GENERATOR), makeTmpFileCopyGroup(NULL_X32_CONFIG.getBuildDir() + "/execserver", "bin/linux32", ["*"], EXCLUDE_BUILD_FILES), makeTmpFileCopyGroup(NULL_X32_CONFIG.getBuildDir() + "/executor", "bin/linux32", ["*"], EXCLUDE_BUILD_FILES), ]) LINUX_X64_COMMON_BINARIES = Module("Linux x64 Common Binaries", [ BuildTarget (NULL_X64_CONFIG, ANY_UNIX_GENERATOR), makeTmpFileCopyGroup(NULL_X64_CONFIG.getBuildDir() + "/execserver", "bin/linux64", ["*"], EXCLUDE_BUILD_FILES), makeTmpFileCopyGroup(NULL_X64_CONFIG.getBuildDir() + "/executor", "bin/linux64", ["*"], EXCLUDE_BUILD_FILES), ]) # Special module to remove src dir, for example after binary build REMOVE_SOURCES = Module("Remove sources from package", [ RemoveSourcesTarget() ]) # Release configuration ALL_MODULES = [ BASE, DOCUMENTATION, GLSHARED, GLES2, GLES3, GLES31, EGL, INTERNAL, EXTERNAL_SRCS, ] ALL_BINARIES = [ LINUX_X64_COMMON_BINARIES, ANDROID_BINARIES, ] RELEASE_CONFIGS = { "src": ALL_MODULES, "src-bin": ALL_MODULES + ALL_BINARIES, "bin": ALL_MODULES + ALL_BINARIES + [REMOVE_SOURCES], } def parseArgs (): parser = argparse.ArgumentParser(description = "Build release package") parser.add_argument("-c", "--config", dest="config", choices=RELEASE_CONFIGS.keys(), required=True, help="Release configuration") parser.add_argument("-n", "--name", dest="name", required=True, help="Package-specific name") parser.add_argument("-v", "--version", dest="version", required=True, help="Version code") return parser.parse_args() if __name__ == "__main__": args = parseArgs() config = ReleaseConfig(args.name, args.version, RELEASE_CONFIGS[args.config]) makeRelease(config)