Home | History | Annotate | Download | only in external
      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 sys
     25 import shutil
     26 import tarfile
     27 import hashlib
     28 import argparse
     29 import subprocess
     30 import ssl
     31 import stat
     32 
     33 sys.path.append(os.path.join(os.path.dirname(__file__), "..", "scripts"))
     34 
     35 from build.common import *
     36 
     37 EXTERNAL_DIR	= os.path.realpath(os.path.normpath(os.path.dirname(__file__)))
     38 
     39 def computeChecksum (data):
     40 	return hashlib.sha256(data).hexdigest()
     41 
     42 def onReadonlyRemoveError (func, path, exc_info):
     43 	os.chmod(path, stat.S_IWRITE)
     44 	os.unlink(path)
     45 
     46 class Source:
     47 	def __init__(self, baseDir, extractDir):
     48 		self.baseDir		= baseDir
     49 		self.extractDir		= extractDir
     50 
     51 	def clean (self):
     52 		fullDstPath = os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
     53 		# Remove read-only first
     54 		readonlydir = os.path.join(fullDstPath, ".git", "objects", "pack")
     55 		if os.path.exists(readonlydir):
     56 			shutil.rmtree(readonlydir, onerror = onReadonlyRemoveError )
     57 		if os.path.exists(fullDstPath):
     58 			shutil.rmtree(fullDstPath, ignore_errors=False)
     59 
     60 class SourcePackage (Source):
     61 	def __init__(self, url, filename, checksum, baseDir, extractDir = "src", postExtract=None):
     62 		Source.__init__(self, baseDir, extractDir)
     63 		self.url			= url
     64 		self.filename		= filename
     65 		self.checksum		= checksum
     66 		self.archiveDir		= "packages"
     67 		self.postExtract	= postExtract
     68 
     69 	def clean (self):
     70 		Source.clean(self)
     71 		self.removeArchives()
     72 
     73 	def update (self, cmdProtocol = None):
     74 		if not self.isArchiveUpToDate():
     75 			self.fetchAndVerifyArchive()
     76 
     77 		if self.getExtractedChecksum() != self.checksum:
     78 			Source.clean(self)
     79 			self.extract()
     80 			self.storeExtractedChecksum(self.checksum)
     81 
     82 	def removeArchives (self):
     83 		archiveDir = os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir)
     84 		if os.path.exists(archiveDir):
     85 			shutil.rmtree(archiveDir, ignore_errors=False)
     86 
     87 	def isArchiveUpToDate (self):
     88 		archiveFile = os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir, pkg.filename)
     89 		if os.path.exists(archiveFile):
     90 			return computeChecksum(readFile(archiveFile)) == self.checksum
     91 		else:
     92 			return False
     93 
     94 	def getExtractedChecksumFilePath (self):
     95 		return os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir, "extracted")
     96 
     97 	def getExtractedChecksum (self):
     98 		extractedChecksumFile = self.getExtractedChecksumFilePath()
     99 
    100 		if os.path.exists(extractedChecksumFile):
    101 			return readFile(extractedChecksumFile)
    102 		else:
    103 			return None
    104 
    105 	def storeExtractedChecksum (self, checksum):
    106 		checksum_bytes = checksum.encode("utf-8")
    107 		writeFile(self.getExtractedChecksumFilePath(), checksum_bytes)
    108 
    109 	def connectToUrl (self, url):
    110 		result = None
    111 
    112 		if sys.version_info < (3, 0):
    113 			from urllib2 import urlopen
    114 		else:
    115 			from urllib.request import urlopen
    116 
    117 		if args.insecure:
    118 			print("Ignoring certificate checks")
    119 			ssl_context = ssl._create_unverified_context()
    120 			result = urlopen(url, context=ssl_context)
    121 		else:
    122 			result = urlopen(url)
    123 
    124 		return result
    125 
    126 	def fetchAndVerifyArchive (self):
    127 		print("Fetching %s" % self.url)
    128 
    129 		req			= self.connectToUrl(self.url)
    130 		data		= req.read()
    131 		checksum	= computeChecksum(data)
    132 		dstPath		= os.path.join(EXTERNAL_DIR, self.baseDir, self.archiveDir, self.filename)
    133 
    134 		if checksum != self.checksum:
    135 			raise Exception("Checksum mismatch for %s, expected %s, got %s" % (self.filename, self.checksum, checksum))
    136 
    137 		if not os.path.exists(os.path.dirname(dstPath)):
    138 			os.mkdir(os.path.dirname(dstPath))
    139 
    140 		writeFile(dstPath, data)
    141 
    142 	def extract (self):
    143 		print("Extracting %s to %s/%s" % (self.filename, self.baseDir, self.extractDir))
    144 
    145 		srcPath	= os.path.join(EXTERNAL_DIR, self.baseDir, self.archiveDir, self.filename)
    146 		tmpPath	= os.path.join(EXTERNAL_DIR, ".extract-tmp-%s" % self.baseDir)
    147 		dstPath	= os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
    148 		archive	= tarfile.open(srcPath)
    149 
    150 		if os.path.exists(tmpPath):
    151 			shutil.rmtree(tmpPath, ignore_errors=False)
    152 
    153 		os.mkdir(tmpPath)
    154 
    155 		archive.extractall(tmpPath)
    156 		archive.close()
    157 
    158 		extractedEntries = os.listdir(tmpPath)
    159 		if len(extractedEntries) != 1 or not os.path.isdir(os.path.join(tmpPath, extractedEntries[0])):
    160 			raise Exception("%s doesn't contain single top-level directory" % self.filename)
    161 
    162 		topLevelPath = os.path.join(tmpPath, extractedEntries[0])
    163 
    164 		if not os.path.exists(dstPath):
    165 			os.mkdir(dstPath)
    166 
    167 		for entry in os.listdir(topLevelPath):
    168 			if os.path.exists(os.path.join(dstPath, entry)):
    169 				raise Exception("%s exists already" % entry)
    170 
    171 			shutil.move(os.path.join(topLevelPath, entry), dstPath)
    172 
    173 		shutil.rmtree(tmpPath, ignore_errors=True)
    174 
    175 		if self.postExtract != None:
    176 			self.postExtract(dstPath)
    177 
    178 class GitRepo (Source):
    179 	def __init__(self, httpsUrl, sshUrl, revision, baseDir, extractDir = "src"):
    180 		Source.__init__(self, baseDir, extractDir)
    181 		self.httpsUrl	= httpsUrl
    182 		self.sshUrl		= sshUrl
    183 		self.revision	= revision
    184 
    185 	def detectProtocol(self, cmdProtocol = None):
    186 		# reuse parent repo protocol
    187 		proc = subprocess.Popen(['git', 'ls-remote', '--get-url', 'origin'], stdout=subprocess.PIPE)
    188 		(stdout, stderr) = proc.communicate()
    189 
    190 		if proc.returncode != 0:
    191 			raise Exception("Failed to execute 'git ls-remote origin', got %d" % proc.returncode)
    192 		if (stdout[:3] == 'ssh') or (stdout[:3] == 'git'):
    193 			protocol = 'ssh'
    194 		else:
    195 			# remote 'origin' doesn't exist, assume 'https' as checkout protocol
    196 			protocol = 'https'
    197 		return protocol
    198 
    199 	def selectUrl(self, cmdProtocol = None):
    200 		try:
    201 			if cmdProtocol == None:
    202 				protocol = self.detectProtocol(cmdProtocol)
    203 			else:
    204 				protocol = cmdProtocol
    205 		except:
    206 			# fallback to https on any issues
    207 			protocol = 'https'
    208 
    209 		if protocol == 'ssh':
    210 			if self.sshUrl != None:
    211 				url = self.sshUrl
    212 			else:
    213 				assert self.httpsUrl != None
    214 				url = self.httpsUrl
    215 		else:
    216 			assert protocol == 'https'
    217 			url = self.httpsUrl
    218 
    219 		assert url != None
    220 		return url
    221 
    222 	def update (self, cmdProtocol = None):
    223 		fullDstPath = os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
    224 
    225 		url = self.selectUrl(cmdProtocol)
    226 		if not os.path.exists(fullDstPath):
    227 			execute(["git", "clone", "--no-checkout", url, fullDstPath])
    228 
    229 		pushWorkingDir(fullDstPath)
    230 		try:
    231 			execute(["git", "fetch", "--tags", url, "+refs/heads/*:refs/remotes/origin/*"])
    232 			execute(["git", "checkout", self.revision])
    233 		finally:
    234 			popWorkingDir()
    235 
    236 def postExtractLibpng (path):
    237 	shutil.copy(os.path.join(path, "scripts", "pnglibconf.h.prebuilt"),
    238 				os.path.join(path, "pnglibconf.h"))
    239 
    240 PACKAGES = [
    241 	SourcePackage(
    242 		"http://zlib.net/zlib-1.2.11.tar.gz",
    243 		"zlib-1.2.11.tar.gz",
    244 		"c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
    245 		"zlib"),
    246 	SourcePackage(
    247 		"http://prdownloads.sourceforge.net/libpng/libpng-1.6.27.tar.gz",
    248 		"libpng-1.6.27.tar.gz",
    249 		"c9d164ec247f426a525a7b89936694aefbc91fb7a50182b198898b8fc91174b4",
    250 		"libpng",
    251 		postExtract = postExtractLibpng),
    252 	GitRepo(
    253 		"https://github.com/KhronosGroup/SPIRV-Tools.git",
    254 		None,
    255 		"vulkan-1.1-rc1",
    256 		"spirv-tools"),
    257 	GitRepo(
    258 		"https://github.com/KhronosGroup/glslang.git",
    259 		None,
    260 		"6.2.2596",
    261 		"glslang"),
    262 	GitRepo(
    263 		"https://github.com/KhronosGroup/SPIRV-Headers.git",
    264 		None,
    265 		"vulkan-1.1-rc2",
    266 		"spirv-headers"),
    267 ]
    268 
    269 def parseArgs ():
    270 	versionsForInsecure = ((2,7,9), (3,4,3))
    271 	versionsForInsecureStr = ' or '.join(('.'.join(str(x) for x in v)) for v in versionsForInsecure)
    272 
    273 	parser = argparse.ArgumentParser(description = "Fetch external sources")
    274 	parser.add_argument('--clean', dest='clean', action='store_true', default=False,
    275 						help='Remove sources instead of fetching')
    276 	parser.add_argument('--insecure', dest='insecure', action='store_true', default=False,
    277 						help="Disable certificate check for external sources."
    278 						" Minimum python version required " + versionsForInsecureStr)
    279 	parser.add_argument('--protocol', dest='protocol', default=None, choices=['ssh', 'https'],
    280 						help="Select protocol to checkout git repositories.")
    281 
    282 	args = parser.parse_args()
    283 
    284 	if args.insecure:
    285 		for versionItem in versionsForInsecure:
    286 			if (sys.version_info.major == versionItem[0]):
    287 				if sys.version_info < versionItem:
    288 					parser.error("For --insecure minimum required python version is " +
    289 								versionsForInsecureStr)
    290 				break;
    291 
    292 	return args
    293 
    294 if __name__ == "__main__":
    295 	args = parseArgs()
    296 
    297 	for pkg in PACKAGES:
    298 		if args.clean:
    299 			pkg.clean()
    300 		else:
    301 			pkg.update(args.protocol)
    302