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